CVE-2019-1003024
Description
A sandbox bypass vulnerability exists in Jenkins Script Security Plugin 1.52 and earlier in RejectASTTransformsCustomizer.java that allows attackers with Overall/Read permission to provide a Groovy script to an HTTP endpoint that can result in arbitrary code execution on the Jenkins master JVM.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A sandbox bypass in Jenkins Script Security Plugin 1.52 and earlier lets users with Overall/Read permission execute arbitrary code on the master JVM.
Vulnerability
The Jenkins Script Security Plugin versions 1.52 and earlier contain a sandbox bypass vulnerability in RejectASTTransformsCustomizer.java [1][2]. The previously implemented protections prohibiting unsafe AST transforming annotations such as @Grab could be circumvented using Groovy language features like AnnotationCollector, import aliasing, or referencing annotation types by their full class name [2]. This allows attackers with Overall/Read permission to provide a malicious Groovy script to an HTTP endpoint that bypasses the sandbox [1][2].
Exploitation
An attacker must have Overall/Read permission on the Jenkins instance, or the ability to control a Jenkinsfile or sandboxed Pipeline shared library content in source control [2]. The attacker crafts a Groovy script that uses the bypass techniques (e.g., AnnotationCollector or full class names of prohibited annotations) and submits it to the endpoint normally used for sandboxed scripts, such as Pipeline script execution [2]. No additional authentication or user interaction is required beyond the initial permission [1][2].
Impact
Successful exploitation results in arbitrary code execution on the Jenkins master JVM [1][2]. This compromises the confidentiality, integrity, and availability of the Jenkins controller and all managed builds, credentials, and data [1][2]. The attacker gains full control over the Jenkins master, equivalent to the Jenkins process user [1][2].
Mitigation
The Script Security Plugin version 1.53 fixes this vulnerability by newly prohibiting the use of AnnotationCollector in sandboxed scripts, blocking imports of unsafe annotations, and rejecting both simple and full class names of prohibited annotations during compilation [2][3]. The fix was released as part of the Jenkins Security Advisory 2019-02-19 [2]. Users should upgrade to Script Security Plugin 1.53 or later. No workaround is available for older versions [2].
AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.jenkins-ci.plugins:script-securityMaven | < 1.53 | 1.53 |
Affected products
2- Jenkins project/Jenkins Script Security Pluginv5Range: 1.52 and earlier
Patches
13228c88e84f0[FIXED SECURITY-1318, SECURITY-1319, SECURITY-1320, SECURITY-1321]
2 files changed · +137 −5
src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/RejectASTTransformsCustomizer.java+51 −5 modified@@ -26,22 +26,32 @@ import com.google.common.collect.ImmutableList; import groovy.lang.Grab; +import groovy.lang.GrabConfig; +import groovy.lang.GrabExclude; +import groovy.lang.GrabResolver; +import groovy.lang.Grapes; import groovy.transform.ASTTest; +import groovy.transform.AnnotationCollector; import org.codehaus.groovy.ast.AnnotatedNode; import org.codehaus.groovy.ast.AnnotationNode; import org.codehaus.groovy.ast.ClassCodeVisitorSupport; import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.ImportNode; +import org.codehaus.groovy.ast.ModuleNode; import org.codehaus.groovy.classgen.GeneratorContext; import org.codehaus.groovy.control.CompilationFailedException; import org.codehaus.groovy.control.CompilePhase; import org.codehaus.groovy.control.SourceUnit; import org.codehaus.groovy.control.customizers.CompilationCustomizer; -import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; public class RejectASTTransformsCustomizer extends CompilationCustomizer { - private static final List<Class<? extends Annotation>> BLOCKED_TRANSFORMS = ImmutableList.of(ASTTest.class, Grab.class); + private static final List<String> BLOCKED_TRANSFORMS = ImmutableList.of(ASTTest.class.getCanonicalName(), Grab.class.getCanonicalName(), + GrabConfig.class.getCanonicalName(), GrabExclude.class.getCanonicalName(), GrabResolver.class.getCanonicalName(), + Grapes.class.getCanonicalName(), AnnotationCollector.class.getCanonicalName()); public RejectASTTransformsCustomizer() { super(CompilePhase.CONVERSION); @@ -64,6 +74,28 @@ protected SourceUnit getSourceUnit() { return source; } + @Override + public void visitImports(ModuleNode node) { + if (node != null) { + for (ImportNode importNode : node.getImports()) { + checkImportForBlockedAnnotation(importNode); + } + for (ImportNode importStaticNode : node.getStaticImports().values()) { + checkImportForBlockedAnnotation(importStaticNode); + } + } + } + + private void checkImportForBlockedAnnotation(ImportNode node) { + if (node != null && node.getType() != null) { + for (String blockedAnnotation : getBlockedTransforms()) { + if (blockedAnnotation.equals(node.getType().getName()) || blockedAnnotation.endsWith("." + node.getType().getName())) { + throw new SecurityException("Annotation " + node.getType().getName() + " cannot be used in the sandbox."); + } + } + } + } + /** * If the node is annotated with one of the blocked transform annotations, throw a security exception. * @@ -72,12 +104,26 @@ protected SourceUnit getSourceUnit() { @Override public void visitAnnotations(AnnotatedNode node) { for (AnnotationNode an : node.getAnnotations()) { - for (Class<? extends Annotation> blockedAnnotation : BLOCKED_TRANSFORMS) { - if (blockedAnnotation.getSimpleName().equals(an.getClassNode().getName())) { - throw new SecurityException("Annotation " + blockedAnnotation.getSimpleName() + " cannot be used in the sandbox."); + for (String blockedAnnotation : getBlockedTransforms()) { + if (blockedAnnotation.equals(an.getClassNode().getName()) || blockedAnnotation.endsWith("." + an.getClassNode().getName())) { + throw new SecurityException("Annotation " + an.getClassNode().getName() + " cannot be used in the sandbox."); } } } } } + + private static List<String> getBlockedTransforms() { + List<String> blocked = new ArrayList<>(BLOCKED_TRANSFORMS); + + String additionalBlocked = System.getProperty(RejectASTTransformsCustomizer.class.getName() + ".ADDITIONAL_BLOCKED_TRANSFORMS"); + + if (additionalBlocked != null) { + for (String b : additionalBlocked.split(",")) { + blocked.add(b.trim()); + } + } + + return blocked; + } }
src/test/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScriptTest.java+86 −0 modified@@ -873,5 +873,91 @@ public void blockGrab() throws Exception { containsString("Annotation Grab cannot be used in the sandbox")); } + @Issue("SECURITY-1318") + @Test + public void blockGrapes() throws Exception { + SecureGroovyScript.DescriptorImpl d = r.jenkins.getDescriptorByType(SecureGroovyScript.DescriptorImpl.class); + assertThat(d.doCheckScript("@Grapes([@Grab(group='foo', module='bar', version='1.0')])\ndef foo\n", false).toString(), + containsString("Annotation Grapes cannot be used in the sandbox")); + } + + @Issue("SECURITY-1318") + @Test + public void blockGrabConfig() throws Exception { + SecureGroovyScript.DescriptorImpl d = r.jenkins.getDescriptorByType(SecureGroovyScript.DescriptorImpl.class); + assertThat(d.doCheckScript("@GrabConfig(autoDownload=false)\ndef foo\n", false).toString(), + containsString("Annotation GrabConfig cannot be used in the sandbox")); + } + + @Issue("SECURITY-1318") + @Test + public void blockGrabExclude() throws Exception { + SecureGroovyScript.DescriptorImpl d = r.jenkins.getDescriptorByType(SecureGroovyScript.DescriptorImpl.class); + assertThat(d.doCheckScript("@GrabExclude(group='org.mortbay.jetty', module='jetty-util')\ndef foo\n", false).toString(), + containsString("Annotation GrabExclude cannot be used in the sandbox")); + } + + @Issue("SECURITY-1319") + @Test + public void blockGrabResolver() throws Exception { + SecureGroovyScript.DescriptorImpl d = r.jenkins.getDescriptorByType(SecureGroovyScript.DescriptorImpl.class); + assertThat(d.doCheckScript("@GrabResolver(name='restlet.org', root='http://maven.restlet.org')\ndef foo\n", false).toString(), + containsString("Annotation GrabResolver cannot be used in the sandbox")); + } + + @Issue("SECURITY-1318") + @Test + public void blockArbitraryAnnotation() throws Exception { + try { + System.setProperty(RejectASTTransformsCustomizer.class.getName() + ".ADDITIONAL_BLOCKED_TRANSFORMS", "groovy.transform.Field,groovy.transform.Immutable"); + SecureGroovyScript.DescriptorImpl d = r.jenkins.getDescriptorByType(SecureGroovyScript.DescriptorImpl.class); + assertThat(d.doCheckScript("@Field\ndef foo\n", false).toString(), + containsString("Annotation Field cannot be used in the sandbox")); + } finally { + System.clearProperty(RejectASTTransformsCustomizer.class.getName() + ".ADDITIONAL_BLOCKED_TRANSFORMS"); + } + } + @Issue("SECURITY-1321") + @Test + public void blockAnnotationCollector() throws Exception { + SecureGroovyScript.DescriptorImpl d = r.jenkins.getDescriptorByType(SecureGroovyScript.DescriptorImpl.class); + assertThat(d.doCheckScript("import groovy.transform.*\n" + + "import jenkins.model.Jenkins\n" + + "import hudson.model.FreeStyleProject\n" + + "@AnnotationCollector([ASTTest]) @interface Lol {}\n" + + "@Lol(value={ assert Jenkins.getInstance().createProject(FreeStyleProject.class, \"should-not-exist\") })\n" + + "@Field int x\n" + + "echo 'hello'\n", false).toString(), containsString("Annotation AnnotationCollector cannot be used in the sandbox")); + + assertNull(r.jenkins.getItem("should-not-exist")); + } + + @Issue("SECURITY-1320") + @Test + public void blockFQCN() throws Exception { + SecureGroovyScript.DescriptorImpl d = r.jenkins.getDescriptorByType(SecureGroovyScript.DescriptorImpl.class); + assertThat(d.doCheckScript("import groovy.transform.*\n" + + "import jenkins.model.Jenkins\n" + + "import hudson.model.FreeStyleProject\n" + + "@groovy.transform.ASTTest(value={ assert Jenkins.getInstance().createProject(FreeStyleProject.class, \"should-not-exist\") })\n" + + "@Field int x\n" + + "echo 'hello'\n", false).toString(), containsString("Annotation groovy.transform.ASTTest cannot be used in the sandbox")); + + assertNull(r.jenkins.getItem("should-not-exist")); + } + + @Issue("SECURITY-1320") + @Test + public void blockImportAsBlockedAnnotation() throws Exception { + SecureGroovyScript.DescriptorImpl d = r.jenkins.getDescriptorByType(SecureGroovyScript.DescriptorImpl.class); + assertThat(d.doCheckScript("import groovy.transform.ASTTest as lolwut\n" + + "import jenkins.model.Jenkins\n" + + "import hudson.model.FreeStyleProject\n" + + "@lolwut(value={ assert Jenkins.getInstance().createProject(FreeStyleProject.class, \"should-not-exist\") })\n" + + "int x\n" + + "echo 'hello'\n", false).toString(), containsString("Annotation groovy.transform.ASTTest cannot be used in the sandbox")); + + assertNull(r.jenkins.getItem("should-not-exist")); + } }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- access.redhat.com/errata/RHSA-2019:0739ghsavendor-advisoryx_refsource_REDHATWEB
- github.com/advisories/GHSA-jgpm-2862-q5m8ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2019-1003024ghsaADVISORY
- www.securityfocus.com/bid/107295mitrevdb-entryx_refsource_BID
- github.com/jenkinsci/script-security-plugin/commit/3228c88e84f0b2f24845b6466cae35617e082059ghsaWEB
- jenkins.io/security/advisory/2019-02-19/ghsax_refsource_CONFIRMWEB
- web.archive.org/web/20200227084947/http://www.securityfocus.com/bid/107295ghsaWEB
News mentions
0No linked articles in our index yet.