Security Reference¶
Known risks, mitigations, and hardening decisions for this Jenkins platform.
Docker socket mount¶
Critical — accepted trade-off on single-developer deployments
/var/run/docker.sock is mounted into the Jenkins controller container. Any pipeline
running on this instance can use the socket to create containers, mount host paths, and
escape to the host machine with root-equivalent access.
Mitigations in place:
- Fork PR trust set to Collaborators — untrusted fork PRs require manual approval
- Webhook signature verification prevents fake build triggers
- Only GitHub org members can trigger builds via the
developerrole
Remaining exposure: Any code in your own build agents can access the socket. This is an accepted trade-off for a single-developer platform on a personal machine. For a shared team environment, replace the socket mount with a dedicated Docker daemon over TCP or use Kaniko for container builds.
Untrusted Jenkinsfile execution (fork PRs)¶
Critical — mitigated by fork PR trust setting
The GitHub Organization Folder auto-discovers and runs Jenkinsfile from every repo,
including PRs from forks. A malicious fork PR with a crafted Jenkinsfile could execute
arbitrary code in your build environment.
Mitigation: During Organization Folder setup (Quick Deploy Step 8), set Discover fork pull requests → Trust to:
| Setting | Behavior |
|---|---|
| Collaborators | Fork PRs only run for explicitly added collaborators — recommended |
| Nobody | All fork PRs require manual approval — maximum security |
| Everyone | All fork PRs run automatically — do not use |
Shared library trust¶
High — requires branch protection
melchior runs as system-level Groovy inside Jenkins — not sandboxed like
pipeline script. If that repo is compromised, all pipelines on the platform are affected.
Mitigations:
- Enable branch protection on
melchiorwith required PR reviews - Never allow direct pushes to
mainon that repo - Treat changes to the shared library as elevated-trust changes — they affect every pipeline
Webhook secret¶
High — mitigated by HMAC verification
Without a webhook secret, anyone who discovers your webhook URL can send fake GitHub events and trigger builds.
Mitigation: GITHUB_WEBHOOK_SECRET is set in .env and registered as a Jenkins credential.
The GitHub plugin verifies the HMAC signature on every incoming webhook, rejecting requests
that don't match.
Generate a secret:
Set the same value in .env and in the Jenkins CI App settings on GitHub.
GitHub org restriction¶
High — mitigated by role assignment
Without org restriction, any GitHub user who can authenticate via OAuth gets the developer
role and can trigger builds.
Mitigation: The developer role in casc.yml is assigned to ${GITHUB_ORG} — only
members of that org (or, for personal accounts, only that username's group) receive build
permissions. Non-members can authenticate but receive no permissions.
For personal accounts: set GITHUB_ORG in .env to your GitHub username.
Build agents running as root¶
High — partially mitigated
The Docker agent connector no longer explicitly requests root, but most official Docker Hub
images (python:3.14, node:20, etc.) default to root internally.
Full mitigation path: Build custom agent images that create a non-root user:
Reference this image in casc.yml instead of the official image. This prevents privilege
escalation within the container and limits the blast radius of a container escape.
Docker image pinning¶
Medium — unmitigated by default
Agent templates reference images by tag (python:3.14, node:20). Tags are mutable — a
compromised or broken image update could silently affect all builds.
Recommended mitigation: Pin images to their digest in casc.yml:
Get the digest:
Update digests during planned maintenance windows rather than tracking floating tags.
Secret leakage in build logs¶
Medium — mitigated by credential handling practices
Jenkins masks credentials() values in build logs but does not catch all variants —
base64-encoded secrets, values split across lines, or secrets written to files can appear
in logs unmasked.
Best practices:
- Never
echoa credential value in a pipeline script - Use
withCredentialsblocks and let Jenkins handle masking - Set log retention limits — Manage Jenkins → Configure System → Discard Old Builds (already set to 30 days)
- Restrict log read access to the
developerrole and above (enforced by Role Strategy)
Rate limiting¶
Low — optional hardening via Cloudflare
The Jenkins URL is publicly accessible via Cloudflare Tunnel with no rate limiting. Login enumeration and credential stuffing are possible.
Mitigation: Enable Cloudflare rate limiting on the tunnel domain:
- Go to Cloudflare dashboard → Security → WAF → Rate limiting rules
- Create a rule targeting your Jenkins domain
- Recommended: limit to 20 requests/minute per IP on
/loginand/securityRealm/ - Action: Block or Challenge
Cloudflare's free plan includes basic rate limiting. No changes to config.yml required.
Private key on disk¶
Low — mitigated by gitignore and read-only mount
github-app.pem is stored as a plaintext file on the host machine. If the host is
compromised, the key is immediately available.
Mitigations in place:
- Mounted read-only into the container (
:roflag indocker-compose.yml) - Listed in
.gitignore— cannot be accidentally committed - Limited to only the Jenkins container process
Further hardening: Store the key in a secrets manager (HashiCorp Vault, AWS Secrets Manager) and inject it at runtime. This is outside scope for the current architecture but is the recommended path for production team deployments.