This is a Java agent for use with Jenkins controllers that do not yet have the fixes for SECURITY-3911 and SECURITY-3930.
SECURITY-3911 affects the Remoting library used for communication with build agents. It allows bypassing the class filter during Java deserialization.
SECURITY-3930 affects tar extraction in FilePath. It allows a crafted tar archive to bypass existing path traversal protections.
Important
Jenkins 2.576, LTS 2.568.2 contain additional fixes for security issue that this agent does not protect from. Those security issues have additional prerequisites that make exploitation more difficult. Update Jenkins to protect from all issues fixed in those releases.
This workaround has been tested with a wide range of Jenkins and Remoting releases:
SECURITY-3911 (Remoting): This Java agent will apply the fix on all Remoting releases used in Jenkins 2.164 and newer (early 2019). The fix is not applied if the class is already patched (e.g., Remoting 3385.vf1123fb_515da_ and newer). If transformation for this issue fails for any reason, Jenkins is forcibly stopped.
SECURITY-3930 (FilePath): This Java agent will apply this fix on Jenkins 2.555 and newer, and Jenkins LTS 2.541.3. Older releases lack the fix for SECURITY-3657, which is a prerequisite for this patch to be applied. The fix is not applied if the class is already patched (Jenkins 2.576 and newer, and LTS 2.568.2 and newer). If transformation fails, a message is logged.
Important
Remove this Java agent as soon as you update to a Jenkins release with the fixes (2.576 or newer, LTS 2.568.2 or newer). It is fairly likely for this Java agent to start failing, potentially in subtle ways, once the Java classes it tries to patch are changed further as part of regular development.
Download the workaround JAR and add it as a Java agent when starting the Jenkins controller:
java -javaagent:/path/to/security3911-3930-workaround.jar -jar jenkins.warThe Java agent must be on the controller process. It does not need to be deployed to build agents.
If you start Jenkins via a service wrapper, package manager, or jenkins.xml (Windows), add the -javaagent argument to the JVM options in the relevant configuration file before the -jar jenkins.war argument.
For example in /etc/default/jenkins (Debian/Ubuntu packages):
JAVA_ARGS="-javaagent:/path/to/security3911-3930-workaround.jar"
The following Java system properties change the Java agent's behavior. Set them as -D JVM arguments before the -javaagent argument.
-
io.jenkins.security.Security39113930Workaround.DISABLESet totrueto disable all class transformations. Not recommended in production. -
io.jenkins.security.Security39113930Workaround.SKIP_SHUTDOWNSet totrueto log a failure rather than stopping Jenkins when the mandatory transformation (SECURITY-3911) fails. Only recommended in specific situations, such as when closely monitoring log output (see below). Jenkins will not be protected if this is set and transformation fails.
Log messages are written directly to stderr and use the prefix SECURITY-3911/3930 Workaround: to be compatible with the --logfile argument to java -jar jenkins.war.
As a result, the log messages from this Java agent do not appear in the System Log UI.
The following scripts can be used to confirm the workarounds are active (or Jenkins has already been updated). Navigate to the Jenkins script console in "Manage Jenkins", enter a script there, and press "Run". The output will tell you whether Jenkins is vulnerable or protected.
The workaround for SECURITY-3911 is expected to work on Jenkins 2.164 and newer.
def notLoader = new ClassLoader(null) {
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
throw new ClassNotFoundException(name)
}
}
def blockingFilter = new hudson.remoting.ClassFilter() {
public boolean isBlacklisted(Class c) {
return c == java.util.concurrent.atomic.AtomicLong
}
public boolean isBlacklisted(String name) {
return false
}
}
def baos = new ByteArrayOutputStream()
new ObjectOutputStream(baos).with { writeObject(new java.util.concurrent.atomic.AtomicLong()); close() }
try {
// We could use ClassFilter.DEFAULT here, but if AtomicLong is added to the allowlist, this will fail unexpectedly
def ois = new hudson.remoting.ObjectInputStreamEx(new ByteArrayInputStream(baos.toByteArray()), notLoader, blockingFilter)
def obj = ois.readObject()
ois.close()
println "VULNERABLE: readObject returned ${obj?.class?.name} — SECURITY-3911 fix is NOT active"
} catch (SecurityException e) {
println "PROTECTED: filter.check(Class) blocked in catch branch — SECURITY-3911 fix is active. ${e.message}"
} catch (Exception e) {
print "Unexpected Exception (unexpected message): "
e.printStackTrace(out)
}The workaround for SECURITY-3930 is expected to work on Jenkins 2.555 and newer, and Jenkins LTS 2.541.3.
def baos = new ByteArrayOutputStream()
def tos = new org.apache.tools.tar.TarOutputStream(baos)
def entry = new org.apache.tools.tar.TarEntry(".", org.apache.tools.tar.TarConstants.LF_SYMLINK)
entry.setLinkName("/test")
tos.putNextEntry(entry)
tos.closeEntry()
tos.close()
File tempDir = java.nio.file.Files.createTempDirectory("jenkins-readFromTar-test-").toFile()
try {
hudson.FilePath.readFromTar("test.tar", tempDir, new ByteArrayInputStream(baos.toByteArray()), java.nio.charset.StandardCharsets.UTF_8)
println "VULNERABLE to SECURITY-3930: readFromTar returned normally"
} catch (IOException e) {
Exception actual = e.cause?:e
if (actual.message?.contains("non-directory entry that resolves to base directory")) {
println "PROTECTED from SECURITY-3930: Fix is active. Exception: ${e.message}"
} else {
print "Unexpected IOException (unexpected message): "
e.printStackTrace(out)
}
} finally {
tempDir.deleteDir()
}Licensed under the terms of the MIT License. Copyright 2026 CloudBees, Inc.