reviewing and hardening jsPDF fixes for PDF injection and XSS
Two CVEs in jsPDF — PDF Object Injection (CVE-2026-31898) and HTML Injection/XSS (CVE-2026-31938). I independently discovered the same vulnerabilities and helped review and harden the fixes as remediation reviewer.

What is jsPDF? jsPDF is one of the most popular JavaScript libraries for generating PDFs in the browser and Node.js. It's used by countless web applications for generating invoices, reports, certificates, and documents client-side.
how i got involved
i was auditing npm packages when i found two vulnerabilities in jsPDF that allowed attackers to inject arbitrary content into generated PDFs and browser contexts. i emailed the maintainer @HackbrettXXX with my findings and a detailed PoC gist.
what i didn't know was that another researcher, @sofianeelhor, had independently reported the same issues and already opened the advisory process. the maintainer added me as a collaborator to help review and harden the fixes. credit where it's due — sofianeelhor found and reported these first, and his DOM construction approach for the XSS fix was cleaner than my initial string-patching recommendation.
i ended up contributing defense-in-depth hardening: hex color validation for the annotation injection, fixing a pre-existing double-hash bug, and expanding the test coverage.
CVE-2026-31898: PDF object injection via annotation color (high)
the createAnnotation method accepts a color parameter for free-text annotations. this color value is embedded directly into the PDF object stream without sanitization. an attacker can inject arbitrary PDF objects — including JavaScript actions — through the color parameter:
import { jsPDF } from 'jspdf'
const doc = new jsPDF();
// This color value breaks out of the PDF string context
// and injects a Launch action
const payload = '000000) /AA <</E <</S /Launch /F (calc.exe)>>>> (';
doc.createAnnotation({
type: 'freetext',
bounds: { x: 10, y: 10, w: 120, h: 20 },
contents: 'hello',
color: payload
});
doc.save('malicious.pdf');
when the resulting PDF is opened, the injected action triggers. the impact depends on the PDF viewer — some execute JavaScript actions, others may launch applications. the injection primitive is real, though execution is viewer-dependent.
severity: High (CVSS 8.1) — CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N
CVE-2026-31938: HTML injection in output methods (critical)
this one is more severe. jsPDF's output() method has several modes that open PDFs in new browser windows: pdfobjectnewwindow, pdfjsnewwindow, and dataurlnewwindow. these modes build HTML using string concatenation and render it with document.write(). several parameters are interpolated directly into the HTML without escaping:
import { jsPDF } from 'jspdf';
const doc = new jsPDF();
// Attacker-controlled filename breaks out of the iframe
const payload = 'x"></iframe><script>alert(document.domain)</script><iframe src="';
doc.output('pdfjsnewwindow', {
filename: payload,
pdfJsUrl: 'viewer.html'
});
the vulnerable parameters:
pdfobjectnewwindow: thepdfObjectUrloption AND the entire options object (JSON-serialized verbatim into HTML)pdfjsnewwindow: thepdfJsUrlandfilenameoptionsdataurlnewwindow: thefilenameoption
in the real-world threat model: an application stores attacker-controlled metadata (report name, export filename, viewer config). another user — say an admin reviewing reports — opens a PDF preview. the app calls one of these output methods with the attacker's data. jsPDF builds HTML with document.write(), and the attacker's script executes in the victim's browser context.
severity: Critical (CVSS 9.6) — CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:L
the fixes
sofianeelhor's fix replaced the document.write() approach with proper DOM construction for the new-window output paths — building the HTML programmatically instead of via string concatenation. this is the right architectural fix: you can't inject HTML if you're not building HTML from strings.
for the annotation injection, the fix sanitizes the color value in the PDF string serialization context.
my contributions as remediation reviewer:
- added hex color validation as defense-in-depth (reject non-hex values before they reach the PDF serializer)
- fixed a pre-existing double-hash bug in color handling
- expanded test coverage for the injection vectors
both fixes shipped in jsPDF 4.2.1.
timeline
| Date | Event |
|---|---|
| Early Mar 2026 | @sofianeelhor reports both vulnerabilities via GitHub Security Advisory |
| Mar 7, 2026 | i independently discover the same issues, email maintainer with PoC gist |
| Mar 10, 2026 | maintainer adds me as collaborator to review fixes |
| Mar 10, 2026 | CVE-2026-31898 and CVE-2026-31938 assigned by GitHub |
| Mar 17, 2026 | both advisories published, jsPDF 4.2.1 released |
references
- GHSA-7x6v-j9x4-qf24 — PDF Object Injection (CVE-2026-31898)
- GHSA-wfv2-pwc8-crg5 — HTML Injection (CVE-2026-31938)
- My PoC gist
if you use jsPDF
upgrade to 4.2.1 or later:
npm install jspdf@latest
reported by @sofianeelhor. remediation review by Doruk Tan Öztürk (@peaktwilight). fixes by sofianeelhor and peaktwilight.