VYPR
High severityNVD Advisory· Published Feb 19, 2026· Updated Feb 19, 2026

jsPDF has PDF Object Injection via Unsanitized Input in addJS Method

CVE-2026-25755

Description

jsPDF is a library to generate PDFs in JavaScript. Prior to 4.2.0, user control of the argument of the addJS method allows an attacker to inject arbitrary PDF objects into the generated document. By crafting a payload that escapes the JavaScript string delimiter, an attacker can execute malicious actions or alter the document structure, impacting any user who opens the generated PDF. The vulnerability has been fixed in jspdf@4.2.0. As a workaround, escape parentheses in user-provided JavaScript code before passing them to the addJS method.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
jspdfnpm
< 4.2.04.2.0

Affected products

1

Patches

1
56b46d45b052

Merge commit from fork

https://github.com/parallax/jsPDF0XJacksFeb 19, 2026via ghsa
3 files changed · +78 3
  • src/modules/javascript.js+25 3 modified
    @@ -41,11 +41,31 @@ import { jsPDF } from "../jspdf.js";
        * @returns {jsPDF}
        */
       jsPDFAPI.addJS = function(javascript) {
    -    // FIX: Move variables inside function scope to prevent shared state
    -    // between multiple jsPDF instances
         var jsNamesObj;
         var jsJsObj;
    -    var text = javascript;
    +    // Escape only unescaped parentheses, without double-escaping already escaped ones
    +    function escapeParens(str) {
    +      let out = "";
    +      for (let i = 0; i < str.length; i++) {
    +        const ch = str[i];
    +        if (ch === "(" || ch === ")") {
    +          // Count preceding backslashes to determine if the paren is already escaped
    +          let bs = 0;
    +          for (let j = i - 1; j >= 0 && str[j] === "\\"; j--) {
    +            bs++;
    +          }
    +          if (bs % 2 === 0) {
    +            out += "\\" + ch;
    +          } else {
    +            out += ch;
    +          }
    +        } else {
    +          out += ch;
    +        }
    +      }
    +      return out;
    +    }
    +    const text = escapeParens(javascript);
     
         this.internal.events.subscribe("postPutResources", function() {
           jsNamesObj = this.internal.newObject();
    @@ -57,10 +77,12 @@ import { jsPDF } from "../jspdf.js";
           jsJsObj = this.internal.newObject();
           this.internal.out("<<");
           this.internal.out("/S /JavaScript");
    +      // The sanitized 'text' is now safe to be enclosed in parentheses
           this.internal.out("/JS (" + text + ")");
           this.internal.out(">>");
           this.internal.out("endobj");
         });
    +
         this.internal.events.subscribe("putCatalog", function() {
           if (jsNamesObj !== undefined && jsJsObj !== undefined) {
             this.internal.out("/Names <</JavaScript " + jsNamesObj + " 0 R>>");
    
  • test/reference/autoprint-js.pdf+0 0 modified
  • test/specs/javascript.spec.js+53 0 added
    @@ -0,0 +1,53 @@
    +/* global describe, it, jsPDF, expect, loadGlobals */
    +/**
    + * jsPDF javascript plugin tests
    + */
    +
    +describe("Module: JavaScript", () => {
    +  beforeAll(loadGlobals);
    +
    +  it("should correctly escape parentheses in addJS", () => {
    +    const doc = new jsPDF();
    +    doc.addJS("alert('Hello (world)');");
    +    const output = doc.output();
    +    expect(output).toContain("/JS (alert\\('Hello \\(world\\)'\\);)");
    +  });
    +
    +  it("should correctly escape nested parentheses in addJS", () => {
    +    const doc = new jsPDF();
    +    doc.addJS("function test() { alert('((nested))'); }");
    +    const output = doc.output();
    +    expect(output).toContain(
    +      "/JS (function test\\(\\) { alert\\('\\(\\(nested\\)\\)'\\); })"
    +    );
    +  });
    +
    +  it("should not double-escape parentheses in addJS", () => {
    +    const doc = new jsPDF();
    +    doc.addJS("alert('Hello \\(world\\)');");
    +    const output = doc.output();
    +    expect(output).toContain("/JS (alert\\('Hello \\(world\\)'\\);)");
    +  });
    +
    +  it("should not double-escape parentheses at the start in addJS", () => {
    +    const doc = new jsPDF();
    +    doc.addJS("\\(");
    +    const output = doc.output();
    +    expect(output).toContain("/JS (\\()");
    +  });
    +
    +  it("should escape parentheses at the start in addJS", () => {
    +    const doc = new jsPDF();
    +    doc.addJS("(");
    +    const output = doc.output();
    +    expect(output).toContain("/JS (\\()");
    +  });
    +
    +  it("should escape parentheses after escaped backslash in addJS", () => {
    +    const doc = new jsPDF();
    +    doc.addJS("\\\\(\\\\)");
    +    const output = doc.output();
    +
    +    expect(output).toContain("/JS (\\\\\\(\\\\\\))");
    +  });
    +});
    

Vulnerability mechanics

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

References

6

News mentions

0

No linked articles in our index yet.