Skip to content

CVE-2026-84649: Verified Reproduction

CVE-2026-84649: Jenkins Stapler: CSRF crumb exposed in dynamically generated JavaScript endpoint SECURITY-3607

CVE-2026-84649 is verified against jenkinsci/jenkins · github. Vulnerability class: CSRF. This medium reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00350.

REPRO-2026-00350 jenkinsci/jenkins · github CSRF Sep 11, 2026 CVE entry .txt
Severity
MEDIUM
Confidence
HIGH
Reproduced in
51m 56s
Tool calls
472
Spend
$20.92
01 · Overview

What Is CVE-2026-84649?

CVE-2026-84649 is a medium-severity CSRF vulnerability affecting jenkinsci/jenkins. Pruva has independently reproduced it and publishes a verified, runnable proof-of-concept (reproduction REPRO-2026-00350).

02 · Severity & CVSS

CVE-2026-84649 Severity

CVE-2026-84649 is rated medium severity.

MEDIUM threat level
Weakness CWE-352 — Cross-Site Request Forgery (CSRF)

Medium — meaningful risk under specific conditions. Schedule a fix in the normal cycle.

How to Reproduce CVE-2026-84649

$ pruva-verify REPRO-2026-00350
or curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00350/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-84649

Authorization bypass — reproduced
  • reached the target end-to-end
  • full exploit chain demonstrated
  • on the real production code path
  • high confidence
  • the upstream fix blocks the same trigger
Trigger

A same-site sibling-host HTML page that includes the Jenkins /$stapler/bound/script endpoint, captures its crumb argument, and submits it in a cross-origin form

Attack chain
  1. Chromium victim login
  2. attacker.example.test page
  3. GET /$stapler/bound/script/whatever
  4. forged POST /me/descriptorByName/jenkins.security.ApiTokenProperty/generateNewToken
How the agent worked 792 events · 472 tool calls · 52 min
52 minDuration
472Tool calls
45Reasoning steps
792Events
41Dead-ends
Agent activity over 52 min
Policy
1
Support
59
Repro
503
Judge
34
Variant
190
Verify
1
0:0051:44

Root Cause and Exploit Chain for CVE-2026-84649

Versions: component: Jenkins core's Stapler integration and Stapler's dynamic JavaScript proxy endpoint /$stapler/bound/script/....

Jenkins 2.579 bundles Stapler 2107.v8dfcb_e8ed317, whose dynamically generated JavaScript proxy endpoint serializes the authenticated user's CSRF crumb as a JavaScript string literal. A page controlled by an attacker on a sibling host in the same site can load that endpoint as a script, define makeStaplerProxy, capture the literal, and submit a cross-origin HTML form carrying the stolen crumb. In the current production-path proof, real Chromium logged in a victim, visited attacker.example.test, loaded the script from jenkins.example.test, recovered the crumb, and created a Jenkins API token through a POST that returned HTTP 403 without the crumb and HTTP 200 with the stolen crumb. Jenkins 2.580 replaces the literal with a DOM expression and is not vulnerable through this endpoint.

  • Affected component: Jenkins core's Stapler integration and Stapler's dynamic JavaScript proxy endpoint /$stapler/bound/script/....
  • Affected versions: Stapler 1839.ved17667b_a_eb_5 through 2107.v8dfcb_e8ed317 inclusive, except 2088.2093.vd7c3e58008a_6; Jenkins weekly 2.447 through 2.579 inclusive; Jenkins LTS 2.452.1 through 2.568.2 inclusive.
  • Risk and consequences: High according to Jenkins SECURITY-3878 (the ticket metadata said medium). The prerequisite is attacker-controlled active content on the same site as Jenkins, such as a sibling subdomain; the Resource Root URL being configured on that same site makes this particularly impactful. A victim must visit the attacker content while logged in. The attacker page can then authorize state-changing actions within the victim's Jenkins permissions. This run demonstrated creation of a new API token in the victim account.

Impact Parity

  • Disclosed/claimed maximum impact: CSRF crumb theft leading to full authenticated-session action forgery (authz_bypass).
  • Reproduced impact from this run: A real sibling-host attacker page exfiltrated the victim's crumb in Chromium and used an ordinary cross-origin HTML form to create a victim API token. An identical state-changing POST without the stolen crumb returned HTTP 403; the forged POST returned HTTP 200 and produced token UUID e81361f4-1998-41ca-98e0-dd36d6a7b747 in the final run.
  • Parity: full.
  • Not demonstrated: No stronger impact than actions already authorized to the victim was claimed or tested. The issued secret token value is intentionally not copied into this report.

Root Cause

Jenkins initializes Stapler's org.kohsuke.stapler.CrumbIssuer adapter in core/src/main/java/hudson/security/csrf/CrumbIssuer.java. In the vulnerable release, the adapter implements issueCrumb by returning Jenkins.get().getCrumbIssuer().getCrumb(request). Stapler's Bound.getProxyScript consumes that value when rendering its dynamic proxy resource and single-quotes it in output such as:

varname = makeStaplerProxy('/whatever','SESSION_CRUMB',['methodname']);

Script inclusion is not protected by the browser same-origin read restriction in the way fetch is: a sibling-host page can load this JavaScript and provide its own makeStaplerProxy function to receive the literal argument. Because the sibling hosts are same-site, a victim's Jenkins session cookie is also eligible for the subsequent form POST. The endpoint therefore reveals the second anti-CSRF factor to the party that can already induce the browser to send the first factor.

Fix commit 95e24c6cfa75efd4a7c079ef091df6fd02748d1a ([SECURITY-3878]) changes the adapter to override getCrumbExpression() and return document.head.dataset.crumbValue when CSRF protection is enabled. Stapler 2117.vb_66952503166 then emits:

varname = makeStaplerProxy('/whatever',document.head.dataset.crumbValue,['methodname']);

The secret is no longer present in the cross-host script response. Jenkins 2.580 contains the fix; 2.579 does not. The tested peeled release commits were 9095ea3a5c5e7dcd392695a5dd880af1c9910ddf and 497de4961ad80d97e26bfdeb0d2e40442a84ecb0 respectively.

Reproduction Steps

  1. Run bundle/repro/reproduction_steps.sh from any directory. The script reads PRUVA_ROOT or resolves the bundle path itself.
  2. The script pulls and verifies these exact images:
    • vulnerable Jenkins 2.579 JDK 21: sha256:a7342867ea33efaacf825229d50b7fc77c144ecada9719ab4e32419f5d7412be;
    • fixed Jenkins 2.580 JDK 21: sha256:0e50a5b11ac14f3b84e529d725ed3a1c4b17ba16188dfa8d9a0189428b0839b1;
    • Playwright v1.55.0 Noble: sha256:b27e719ecbfef153e13fd24e8341736733bf2658b229677eb21ff57ff5d7fb29.
  3. It starts real Jenkins controllers, creates and form-authenticates a victim, executes bundle/repro/same_site_attack.js in real Chromium, and serves an in-browser attacker page at attacker.example.test which loads the Jenkins script endpoint at sibling jenkins.example.test.
  4. It submits the state-changing token-generation action with an actual cross-origin HTML form using the captured crumb. It also runs two isolated direct vulnerable attempts and two Jenkins 2.580 fixed negative-control attempts.
  5. Expected success output is CONFIRMED: Jenkins 2.579 exposed a session crumb ...; Jenkins 2.580 did not. The script exits nonzero unless the no-crumb request is 403, the vulnerable stolen-crumb request is 200, and both fixed attempts omit the literal and emit the safe DOM expression.

Evidence

  • Real browser exploit result: bundle/repro/proof/browser-same-site-result.json
    • attacker_origin=http://attacker.example.test:8081/attack.html
    • victim_authenticated_as=victim
    • no_crumb_http=403
    • stolen_crumb_http=200
    • created_token_uuid=e81361f4-1998-41ca-98e0-dd36d6a7b747
  • Browser-created state-change marker: bundle/repro/proof/browser-same-site-marker.txt.
  • Browser/Jenkins service transcript: bundle/repro/proof/browser-same-site.service.log.
  • Two direct vulnerable attempt summaries: bundle/repro/proof/vulnerable_1.result.txt and vulnerable_2.result.txt. Each records authentication, no-crumb HTTP 403, equality between the dynamic-script literal and the authenticated session crumb, and stolen-crumb HTTP 200.
  • State-changing responses: bundle/repro/proof/vulnerable_1.stolen_crumb.response.body and vulnerable_2.stolen_crumb.response.body contain Jenkins status: ok and fresh token UUIDs.
  • Fixed negative controls: bundle/repro/proof/fixed_1.dynamic-js.response.body and fixed_2.dynamic-js.response.body contain document.head.dataset.crumbValue, while both fixed result files record an empty dynamic literal and no attempted forged action.
  • Loaded product/component identity: bundle/repro/proof/*product-component-identity.txt binds the exact WAR SHA-256 and bundled Stapler jars (2107 vulnerable, 2117 fixed).
  • Image identities: bundle/repro/proof/target-identities.txt.
  • Hash binding: bundle/repro/runtime_manifest.json lists 48 finalized proof artifacts and the SHA-256 of each. It identifies the executed vulnerable image and peeled Jenkins commit.
  • Root-cause source evidence: bundle/logs/repro/security-3878-source-diff.txt, vulnerable-source-snippet.txt, fixed-source-snippet.txt, and fix-commit-identity.txt.
  • Repeated execution: bundle/logs/reproduction_steps-redacted1.stdout and reproduction_steps-redacted2.stdout both contain the confirmation line; both corresponding stderr logs are empty.

No sanitizer was used. This was a production HTTP and browser workflow against the official Jenkins images, not a parser/unit harness.

Recommendations / Next Steps

  • Upgrade Jenkins weekly to 2.580 or later, or LTS to 2.568.3 or later.
  • If immediate upgrade is impossible, disable the Resource Root URL feature or place it on a different registrable domain rather than a sibling host on the Jenkins site.
  • Do not host untrusted active content on the Jenkins site.
  • Preserve regression tests that request /$stapler/bound/script/whatever?var=...&methods=... with CSRF enabled and assert that the response contains no crumb-like literal and does contain the DOM expression.
  • Test with a real browser and sibling-host setup so script inclusion and SameSite cookie behavior remain covered.

Additional Notes

  • The exact final script and helper passed twice consecutively after digest pinning and the cross-origin form enhancement. Their SHA-256 values are 487358e21524f0215abf5a00acf9898d07f00b04c995a7fb377285f0bc6f3914 and 88bf6d58888e4ec722c4739c968d8347c730f97ce8d40d21ba0169c1de2f0605.
  • Every controller uses a fresh temporary Docker volume and every invocation removes its containers, network, and labeled volumes.
  • The ticket summary calls this SECURITY-3607; the Jenkins advisory, source test, and fix commit consistently identify it as SECURITY-3878.
  • The browser helper installs the exact playwright@1.55.0 Node package into a temporary volume while reusing the matching immutable Playwright image's Chromium. Network access is therefore required for first-time image and package acquisition.

CVE-2026-84649 Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:004:34
0:00
session startedgpt-5.6-sol · CVE-2026-84649 · REPRO-20
0:08
0:09
web search
0:14
0:46
1:57
web search
1:59
web search
2:12
web search
2:13
web search
2:16
web search
2:19
2:33
2:34
2:36
2:37
web search
2:53
web search
2:55
web search
2:58
web search
3:01
3:17
3:18
web search
3:19
web search
3:21
web search
3:32
3:33
web search
3:34
web search
3:38
4:30
4:32
web search
4:33
web search
4:34
web search

Artifacts and Evidence for CVE-2026-84649

Scripts, logs, diffs, and output captured during the reproduction.

bundle/logs/repro/security-3878-source-diff.txt2.1 KB
bundle/logs/reproduction_steps-form1.stdout0.1 KB
bundle/logs/reproduction_steps-form2.stdout0.1 KB
bundle/logs/reproduction_steps-redacted1.stdout0.1 KB
bundle/logs/reproduction_steps-redacted2.stdout0.1 KB
bundle/repro/proof/browser-same-site-marker.txt0.0 KB
bundle/repro/proof/browser-same-site-result.json0.4 KB
bundle/repro/proof/browser-same-site-summary.txt0.2 KB
bundle/repro/proof/browser-same-site.log0.0 KB
bundle/repro/proof/browser-same-site.service.log3.6 KB
bundle/repro/proof/fixed_1.dynamic-js.request.txt0.1 KB
bundle/repro/proof/fixed_1.dynamic-js.response.headers0.9 KB
bundle/repro/proof/fixed_1.no_crumb.request.txt0.2 KB
bundle/repro/proof/fixed_1.no_crumb.response.body0.6 KB
bundle/repro/proof/fixed_1.no_crumb.response.headers1.0 KB
bundle/repro/proof/fixed_1.product-component-identity.txt0.3 KB
bundle/repro/proof/fixed_1.service.log3.3 KB
bundle/repro/proof/fixed_2.dynamic-js.request.txt0.1 KB
bundle/repro/proof/fixed_2.dynamic-js.response.body0.1 KB
bundle/repro/proof/fixed_2.dynamic-js.response.headers0.9 KB
bundle/repro/proof/fixed_2.no_crumb.request.txt0.2 KB
bundle/repro/proof/fixed_2.no_crumb.response.body0.6 KB
bundle/repro/proof/fixed_2.no_crumb.response.headers1.0 KB
bundle/repro/proof/fixed_2.product-component-identity.txt0.3 KB
bundle/repro/proof/fixed_2.result.txt0.3 KB
bundle/repro/proof/fixed_2.service.log3.3 KB
bundle/repro/proof/target-identities.txt0.3 KB
bundle/repro/proof/vulnerable_1.dynamic-js.request.txt0.1 KB
bundle/repro/proof/vulnerable_1.dynamic-js.response.headers0.9 KB
bundle/repro/proof/vulnerable_1.no_crumb.request.txt0.2 KB
bundle/repro/proof/vulnerable_1.no_crumb.response.body0.6 KB
bundle/repro/proof/vulnerable_1.no_crumb.response.headers1.0 KB
bundle/repro/proof/vulnerable_1.product-component-identity.txt0.3 KB
bundle/repro/proof/vulnerable_1.service.log3.3 KB
bundle/repro/proof/vulnerable_1.stolen_crumb.request.txt0.3 KB
bundle/repro/proof/vulnerable_1.stolen_crumb.response.body0.2 KB
bundle/repro/proof/vulnerable_1.stolen_crumb.response.headers1.0 KB
bundle/repro/proof/vulnerable_2.dynamic-js.request.txt0.1 KB
bundle/repro/proof/vulnerable_2.dynamic-js.response.body0.1 KB
bundle/repro/proof/vulnerable_2.dynamic-js.response.headers0.9 KB
bundle/repro/proof/vulnerable_2.no_crumb.request.txt0.2 KB
bundle/repro/proof/vulnerable_2.no_crumb.response.body0.6 KB
bundle/repro/proof/vulnerable_2.no_crumb.response.headers1.0 KB
bundle/repro/proof/vulnerable_2.product-component-identity.txt0.3 KB
bundle/repro/proof/vulnerable_2.result.txt0.3 KB
bundle/repro/proof/vulnerable_2.service.log3.3 KB
bundle/repro/proof/vulnerable_2.stolen_crumb.request.txt0.3 KB
bundle/repro/proof/vulnerable_2.stolen_crumb.response.body0.2 KB
bundle/repro/proof/vulnerable_2.stolen_crumb.response.headers1.0 KB
bundle/repro/rca_report.md9.0 KB
bundle/repro/reproduction_steps.sh16.6 KB
bundle/repro/runtime_manifest.json9.1 KB
bundle/repro/same_site_attack.js3.9 KB
bundle/repro/validation_verdict.json1.4 KB
08 · How to Fix

How to Fix CVE-2026-84649

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-84649 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-84649

Is CVE-2026-84649 exploitable?

Yes. Pruva independently reproduced CVE-2026-84649 in jenkinsci/jenkins and verified the exploit fires end-to-end in a sandboxed environment. A runnable proof-of-concept script and the full agent transcript are on this page (reproduction REPRO-2026-00350).

How severe is CVE-2026-84649?

CVE-2026-84649 is rated medium severity.

What type of vulnerability is CVE-2026-84649?

CVE-2026-84649 is classified as CWE-352 (Cross-Site Request Forgery (CSRF)), a CSRF vulnerability.

How can I reproduce CVE-2026-84649?

Pruva provides a verified reproduction script on this page. Download it and run it inside an isolated environment such as a container or virtual machine — never against production. The reproduction was confirmed end-to-end by Pruva's automated agents.

Is the CVE-2026-84649 reproduction verified?

Yes. Pruva reproduced CVE-2026-84649 with high confidence in a sandboxed environment, capturing the full agent transcript and artifacts as evidence.
11 · References

References for CVE-2026-84649

Authoritative sources for CVE-2026-84649 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.