VYPR
High severityNVD Advisory· Published Feb 12, 2020· Updated Aug 4, 2024

CVE-2020-2110

CVE-2020-2110

Description

Sandbox protection in Jenkins Script Security Plugin 1.69 and earlier could be circumvented during the script compilation phase by applying AST transforming annotations to imports or by using them inside of other annotations.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Jenkins Script Security Plugin 1.69 and earlier allows sandbox bypass via AST transforming annotations on imports or within annotations.

Vulnerability

Description

The Jenkins Script Security Plugin provides a sandbox for Groovy scripts to prevent malicious code execution. In versions 1.69 and earlier, the sandbox protection can be circumvented during the script compilation phase by applying AST (Abstract Syntax Tree) transforming annotations to import statements or by using such annotations inside other annotations. Examples of AST transforming annotations include @Grab and @ASTTest. This issue arises from an incomplete fix for SECURITY-1266 [1][4].

Attack

Vector

To exploit this vulnerability, an attacker must have Overall/Read permission in Jenkins. The attack can be carried out by submitting a sandboxed script (e.g., via Pipeline) that contains malicious AST transforming annotations on imports or nested within other annotations. The sandbox bypass affects both script execution (typically invoked from plugins like Pipeline) and HTTP endpoints that provide sandboxed script validation [1][2].

Impact

Successful exploitation allows an attacker to bypass the sandbox protection and execute arbitrary code on the Jenkins controller JVM. This could lead to full compromise of the Jenkins instance, including access to sensitive data, credentials, and the ability to perform further attacks [1][3].

Mitigation

The vulnerability is fixed in Script Security Plugin version 1.70. Users should upgrade immediately. No workarounds are available. The fix was applied in commit 1a09bdcf789b87c4e158aacebd40937c64398de3 [4]. This CVE is also mentioned in the Jenkins Security Advisory 2020-02-12 [1] and the corresponding OSS-Security mailing list post [2].

AI Insight generated on May 21, 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.

PackageAffected versionsPatched versions
org.jenkins-ci.plugins:script-securityMaven
< 1.701.70

Affected products

2

Patches

1
1a09bdcf789b

[SECURITY-1713] Block illegal annotations on imports and nested in other annotations

2 files changed · +59 15
  • src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/RejectASTTransformsCustomizer.java+4 0 modified
    @@ -62,6 +62,8 @@ public void call(final SourceUnit source, GeneratorContext context, ClassNode cl
             new RejectASTTransformsVisitor(source).visitClass(classNode);
         }
     
    +    // Note: Methods in this visitor that override methods from the superclass should call the implementation from the
    +    // superclass to ensure that any nested AST nodes are traversed.
         private static class RejectASTTransformsVisitor extends ClassCodeVisitorSupport {
             private SourceUnit source;
     
    @@ -84,6 +86,7 @@ public void visitImports(ModuleNode node) {
                         checkImportForBlockedAnnotation(importStaticNode);
                     }
                 }
    +            super.visitImports(node);
             }
     
             private void checkImportForBlockedAnnotation(ImportNode node) {
    @@ -110,6 +113,7 @@ public void visitAnnotations(AnnotatedNode node) {
                         }
                     }
                 }
    +            super.visitAnnotations(node);
             }
         }
     
    
  • src/test/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SandboxInterceptorTest.java+55 15 modified
    @@ -27,6 +27,7 @@
     import groovy.json.JsonBuilder;
     import groovy.json.JsonDelegate;
     import groovy.lang.GString;
    +import groovy.lang.Grab;
     import groovy.lang.GroovyObject;
     import groovy.lang.GroovyObjectSupport;
     import groovy.lang.GroovyRuntimeException;
    @@ -37,6 +38,7 @@
     import groovy.lang.Script;
     import groovy.text.SimpleTemplateEngine;
     import groovy.text.Template;
    +import groovy.transform.ASTTest;
     import hudson.Functions;
     import hudson.util.IOUtils;
     import java.lang.reflect.Constructor;
    @@ -76,15 +78,12 @@
     import org.junit.Rule;
     import org.junit.Test;
     import org.junit.rules.ErrorCollector;
    -import org.junit.rules.ExpectedException;
     import org.jvnet.hudson.test.Issue;
     
     public class SandboxInterceptorTest {
     
         @Rule public ErrorCollector errors = new ErrorCollector();
     
    -    @Rule public ExpectedException thrown = ExpectedException.none();
    -
         @Test public void genericWhitelist() throws Exception {
             assertEvaluate(new GenericWhitelist(), 3, "'foo bar baz'.split(' ').length");
             assertEvaluate(new GenericWhitelist(), false, "def x = null; x != null");
    @@ -844,24 +843,16 @@ public void explode() {}
         @Issue("SECURITY-1266")
         @Test
         public void blockedASTTransformsASTTest() throws Exception {
    -        GroovyShell shell = new GroovyShell(GroovySandbox.createSecureCompilerConfiguration());
    -
    -        thrown.expect(MultipleCompilationErrorsException.class);
    -        thrown.expectMessage("Annotation ASTTest cannot be used in the sandbox");
    -
    -        shell.parse("import groovy.transform.*\n" +
    -                "@ASTTest(value={ assert true })\n" +
    +        assertAnnotationBlocked(ASTTest.class,
    +                "@groovy.transform.ASTTest(value={ throw new Exception('ASTTest should not have been executed!') })\n" +
                     "@Field int x\n");
         }
     
         @Issue("SECURITY-1266")
         @Test
         public void blockedASTTransformsGrab() throws Exception {
    -        GroovyShell shell = new GroovyShell(GroovySandbox.createSecureCompilerConfiguration());
    -        thrown.expect(MultipleCompilationErrorsException.class);
    -        thrown.expectMessage("Annotation Grab cannot be used in the sandbox");
    -
    -        shell.parse("@Grab(group='foo', module='bar', version='1.0')\n" +
    +        assertAnnotationBlocked(Grab.class,
    +                "@groovy.lang.Grab(group='foo', module='bar', version='1.0')\n" +
                     "def foo\n");
         }
     
    @@ -1281,4 +1272,53 @@ public void scriptInitializersClassSyntax() throws Exception {
                     "import jenkins.model.Jenkins\n" +
                     "({ j = Jenkins.getInstance() -> true })()\n");
         }
    +
    +    @Issue("SECURITY-1713")
    +    @Test
    +    public void blockIllegalAnnotationsOnImports() throws Exception {
    +        assertAnnotationBlocked(ASTTest.class,
    +                "@groovy.transform.ASTTest(value={\n" +
    +                "  throw new Exception('ASTTest should not have been executed!')\n" +
    +                "})\n" +
    +                "import java.lang.Object\n");
    +    }
    +
    +    @Issue("SECURITY-1713")
    +    @Test
    +    public void blockIllegalAnnotationsInAnnotations() throws Exception {
    +        assertAnnotationBlocked(ASTTest.class,
    +                "@groovy.lang.Category(value = {\n" +
    +                "  @groovy.transform.ASTTest(value = {\n" +
    +                "    throw new Exception('ASTTest should not have been executed!')\n" +
    +                "  })\n" +
    +                "  Object\n" +
    +                "})\n" +
    +                "class Foo { }\n");
    +    }
    +
    +    /**
    +     * Checks that the annotation is blocked from being used in the provided script whether it is imported or used via
    +     * fully-qualified class name.
    +     * @param annotation The annotation that will be checked.
    +     * @param script The script to check. It should use the annotation via a fully-qualified class name.
    +     */
    +    private void assertAnnotationBlocked(Class annotation, String script) {
    +        assertAnnotationBlockedInternal(annotation, script);
    +        assertAnnotationBlockedInternal(annotation,
    +                "import " + annotation.getCanonicalName() + "\n" +
    +                script.replaceAll(annotation.getName(), annotation.getSimpleName()));
    +    }
    +
    +    private void assertAnnotationBlockedInternal(Class annotation, String script) {
    +        GroovyShell shell = new GroovyShell(GroovySandbox.createSecureCompilerConfiguration());
    +        try {
    +            shell.parse(script);
    +            fail("Compilation should have failed");
    +        } catch (Exception e) {
    +            assertThat(e, instanceOf(MultipleCompilationErrorsException.class));
    +            assertThat(e.getMessage(), anyOf(
    +                    containsString("Annotation " + annotation.getName() + " cannot be used in the sandbox"),
    +                    containsString("Annotation " + annotation.getSimpleName() + " cannot be used in the sandbox")));
    +        }
    +    }
     }
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

1