Credentials & Secrets in Jenkins
Credentials & Secrets in Jenkins
Secrets are the single most dangerous artifact in any CI/CD system. A leaked credential in a build log, a hardcoded API key in a Jenkinsfile, or a broadly-scoped token shared across every pipeline has ended careers and triggered breach disclosures at companies of every size. Jenkins ships with a mature credentials subsystem — but using it safely at enterprise scale requires understanding how it works, where it stores things, and which patterns lead to silent data leakage.
The Credentials Store
Jenkins stores credentials in an encrypted key-value store backed by the Credentials Plugin (bundled since Jenkins LTS 2.x). The master encryption key lives in $JENKINS_HOME/secrets/master.key; individual credential ciphertexts are stored as XML files under $JENKINS_HOME/credentials.xml (global) or inside each $JENKINS_HOME/jobs/<name>/config.xml (folder/item scope).
The encryption envelope is two-tier: the master key is used to derive a per-credential AES-128 key via a secret seed file (hudson.util.Secret). This means backing up Jenkins without its secrets/ directory renders credentials unrecoverable — and conversely, copying just credentials.xml without the master key is safe as ciphertext.
Credential Types
The Credentials Plugin ships several built-in types; enterprise plugins add more:
- Username with password — the most common; bound to
usernameVariableandpasswordVariable. - Secret text — single opaque string (API tokens, signing keys).
- SSH Username with private key — key entered directly or loaded from a file path on the agent; passphrase stored separately.
- Certificate (PKCS#12) — mutual TLS, code signing. Bound via
KEYSTORE_FILE/KEYSTORE_PASSWORD. - Secret file — arbitrary binary blob (kubeconfig, GCP JSON key). Materialised as a temp file on the agent's workspace; Jenkins cleans it after the step.
- Vault: AppRole / AWS IAM / GCP Workload Identity — provided by the HashiCorp Vault Plugin, AWS Credentials Plugin, etc. No static secret ever stored in Jenkins.
Using Credentials in a Declarative Pipeline
The canonical way to expose a credential inside a pipeline step is the withCredentials block. Jenkins auto-masks any variable bound this way: if the raw value appears in any log line, it is replaced with ****.
echo $SECRET or sh 'printenv' inside a withCredentials block. Jenkins masks exact matches, but base64-encoded or URL-encoded forms of the secret are not masked. Any transformation of the raw value can escape into logs unredacted.
Credential Scoping in Practice
Jenkins supports three meaningful scopes:
- System — available only to Jenkins internals (node connections, mail server). Not usable inside pipeline steps. Use for infra-level credentials.
- Global (domain: Jenkins) — visible to all jobs on the controller. Use only for genuinely shared, low-value credentials (e.g., a read-only Nexus mirror account).
- Folder / Item — recommended for anything sensitive. A credential in
/team-payments/is invisible to jobs in/team-platform/. This is the primary blast-radius control in a multi-team Jenkins instance.
Integrating with External Vaults
At scale, storing secrets inside Jenkins is an anti-pattern. The accepted enterprise approach is to treat Jenkins as a consumer, not a store. Secrets live in HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault; Jenkins retrieves them at runtime using short-lived tokens:
With this pattern, secrets are never persisted in Jenkins: no XML on disk, no database row, no config.xml. Vault enforces TTL, audit logging, and dynamic rotation independently of Jenkins.
Masking Behaviour and Its Limits
Jenkins masking intercepts each println / log write and performs a literal string replacement. Understand its limits in production:
- Masking is per-value, not per-byte-sequence. A 3-character secret (
abc) matches trivially in log noise — avoid short secrets. - Multi-line secrets (PEM keys) are masked line by line only if the Mask Passwords Plugin is active. The built-in masking covers only the exact bound string.
- Archived test reports, JUnit XML, Jacoco HTML, and other artifacts are never masked. A stack trace can contain a secret passed as an argument.
- Agent console output forwarded to external log aggregators (Splunk, ELK) passes through Jenkins masking before transmission — but only if the Pipeline Logging Plugin or equivalent interceptor is in the path.
Credential Rotation without Downtime
The safest rotation strategy is: create the new credential with a new ID, update all pipeline references in source control (Jenkinsfile changes go through PR review), deploy the updated pipelines, then delete the old credential. Never overwrite a credential in-place mid-pipeline-run — in-flight builds hold a reference to the credential ID and will re-read the value; if the old value is now invalid, those builds fail mid-flight in a non-deterministic way.
Least-Privilege Checklist
- Use folder-scoped credentials for every team or project boundary.
- Grant pipelines the narrowest token scope possible (read-only deploy token vs. repo admin token).
- Prefer short-lived dynamic secrets from Vault or cloud IAM over long-lived static tokens.
- Never echo or log secrets, even in a catch block.
- Rotate all credentials on any engineer offboarding or breach suspicion.
- Back up
$JENKINS_HOME/secrets/separately, encrypted, with access auditing.