How Local File Inclusion/Path Traversal Happens in JavaScript PDF Generation and How to Fix It
Summary
CVE-2025-68428 is a critical Local File Inclusion/Path Traversal vulnerability in the popular jsPDF JavaScript library, affecting all versions prior to 4.0.0. In a frontend application using jsPDF 3.0.4, this flaw could allow an attacker to supply crafted path sequences during PDF document generation, potentially exposing files well outside the intended resource directory. The fix is a direct version upgrade — from jspdf@3.0.4 to jspdf@4.0.0 — applied to frontend/package.json and frontend/package-lock.json.
Introduction
The frontend/package-lock.json file is easy to overlook from a security perspective — it's an auto-generated lockfile, rarely edited by hand, and typically treated as infrastructure rather than application code. But it encodes the exact versions of every dependency your application ships, including transitive ones. When the Trivy scanner flagged rule CVE-2025-68428 against this file, it identified that jspdf at version 3.0.4 carries a critical path traversal flaw that could let an attacker read arbitrary files from the host system through the PDF generation pipeline.
This post breaks down what that vulnerability means in practice, how it could be exploited in a real application, and what the upgrade to jsPDF 4.0.0 actually changes.
The Vulnerability Explained
What Is Path Traversal in a PDF Library?
Path traversal (CWE-22) occurs when an application constructs a filesystem path using user-supplied input without properly sanitizing directory-climbing sequences like ../. In the context of a PDF generation library like jsPDF, this can happen when the library resolves references to embedded resources — fonts, images, or other assets — using paths that flow from user-controlled input.
In jsPDF 3.0.4, the library's internal file resolution logic does not adequately restrict the paths it will follow when loading resources for inclusion in a generated document. If your application passes a user-influenced value as a resource path (e.g., a font file path, an image URL resolved locally, or a template asset), jsPDF 3.0.4 may follow traversal sequences to reach files outside the intended asset directory.
The Vulnerable Pattern
Consider a server-side Node.js usage of jsPDF (common in report generation services) where a user can influence the name of a font or image to embed:
// VULNERABLE: jspdf 3.0.4
const { jsPDF } = require('jspdf');
const doc = new jsPDF();
// userSuppliedPath comes from an HTTP request parameter
const fontPath = `/app/assets/fonts/${req.query.fontName}`;
doc.addFont(fontPath, 'CustomFont', 'normal');
doc.save('output.pdf');
If req.query.fontName is set to ../../../../etc/passwd, jsPDF 3.0.4 does not reject or normalize this path. The resolved path becomes /app/assets/fonts/../../../../etc/passwd, which canonicalizes to /etc/passwd. The library then reads and potentially embeds the contents of that file into the generated PDF — or throws an error that leaks path information.
Even in browser-only usage, the same pattern can affect server-side rendering pipelines, PDF generation microservices, or SSR frameworks where jsPDF runs in a Node.js context with access to the local filesystem.
Real-World Impact for This Application
The vulnerability was flagged in frontend/package-lock.json under the jspdf dependency entry. While the scanner noted the code path was not confirmed reachable at the time of assessment, the presence of a critical, remotely exploitable CVE in a production dependency is sufficient cause for immediate remediation. If any code path in the frontend build pipeline, SSR layer, or a Node.js PDF generation service uses jsPDF with user-influenced resource paths, the attack surface is real.
Attack scenario:
1. Attacker identifies that the application generates PDFs with embedded assets.
2. Attacker submits a crafted request with fontName=../../../../etc/shadow or imagePath=../../../../app/config/secrets.json.
3. jsPDF 3.0.4 resolves the traversal path without sanitization.
4. The server reads the target file and either embeds it in the PDF response or leaks an error message confirming the file's existence.
5. Attacker retrieves sensitive credentials, configuration, or private keys.
The Fix
What Changed: jsPDF 3.0.4 → 4.0.0
The remediation is a direct dependency upgrade. In frontend/package.json, the jspdf version constraint was updated from 3.0.4 to 4.0.0, and frontend/package-lock.json was regenerated to lock the resolved version.
Before (frontend/package.json):
{
"dependencies": {
"jspdf": "3.0.4"
}
}
After (frontend/package.json):
{
"dependencies": {
"jspdf": "4.0.0"
}
}
The package-lock.json diff also shows the removal of several libc constraint fields from platform-specific optional dependency entries (e.g., glibc and musl annotations). These changes reflect updated metadata in the 4.0.0 lockfile format, where the dependency tree was regenerated cleanly:
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
While these libc removals are largely metadata changes related to how npm 10+ handles optional platform packages, the critical security change is the version bump itself. jsPDF 4.0.0 introduces proper sanitization of file paths passed to resource-loading functions, preventing the ../ traversal sequences from resolving outside the intended base directory.
Why This Fix Works
jsPDF 4.0.0 addresses CVE-2025-68428 by:
- Normalizing paths before filesystem access — resolving
..segments and comparing the canonical path against the allowed base directory. - Rejecting traversal sequences — inputs containing
../or URL-encoded equivalents (%2e%2e%2f) are rejected before any I/O operation. - Scoping resource resolution — embedded asset paths are validated to remain within the configured asset root.
The fix is entirely backward-compatible for valid inputs: legitimate font names, image paths, and asset references that stay within the intended directory are unaffected.
Prevention & Best Practices
1. Validate Paths at the Application Layer Too
Even with a patched library, never pass raw user input directly as a file path. Validate and allowlist resource identifiers before they reach any file-loading API:
const ALLOWED_FONTS = ['Helvetica', 'Courier', 'Arial'];
if (!ALLOWED_FONTS.includes(req.query.fontName)) {
return res.status(400).json({ error: 'Invalid font name' });
}
2. Canonicalize and Scope Paths Explicitly
If dynamic paths are genuinely required, resolve them to a canonical absolute path and verify they remain under the expected base directory:
const path = require('path');
const BASE_DIR = '/app/assets/fonts';
const requestedFont = req.query.fontName;
const resolvedPath = path.resolve(BASE_DIR, requestedFont);
if (!resolvedPath.startsWith(BASE_DIR + path.sep)) {
throw new Error('Path traversal attempt detected');
}
3. Keep Dependencies Audited and Locked
- Run
npm auditin CI on every pull request. - Use tools like Trivy, Snyk, or Socket.dev to scan
package-lock.jsonfor known CVEs. - Pin exact versions in
package-lock.jsonand regenerate it when upgrading.
4. Apply the Principle of Least Privilege
If your PDF generation service runs in Node.js, restrict its filesystem access using OS-level controls (e.g., chroot, Docker volume mounts scoped to /app/assets) so that even a successful traversal cannot reach sensitive files.
5. Reference Security Standards
- OWASP Path Traversal: https://owasp.org/www-community/attacks/Path_Traversal
- CWE-22: Improper Limitation of a Pathname to a Restricted Directory
- OWASP Input Validation Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html
Key Takeaways
- jsPDF 3.0.4 does not sanitize user-influenced resource paths — any application passing dynamic values to font, image, or asset loading functions is potentially vulnerable to CVE-2025-68428.
frontend/package-lock.jsonis a security artifact, not just a build artifact — the exact versions it locks directly determine your application's CVE exposure.- The
libcmetadata removals in the diff are a side effect of regenerating the lockfile with npm 10+, not the security fix itself; the fix is the version bump from3.0.4to4.0.0. - Path traversal in PDF generation libraries is particularly dangerous because PDF creation often runs with elevated filesystem access to read fonts and images.
- Trivy's static analysis of
package-lock.jsoncaught this before it was confirmed reachable in production — demonstrating the value of scanning dependency lockfiles, not just runtime code.
How Orbis AppSec Detected This
- Source: User-influenced file path values passed to jsPDF resource-loading functions (e.g., font name or image path parameters derived from HTTP request input).
- Sink: jsPDF 3.0.4's internal path resolution logic for embedded document resources, which performs filesystem I/O without canonicalizing or bounding the supplied path.
- Missing control: No normalization of
../sequences and no validation that the resolved canonical path remains within the intended asset base directory. - CWE: CWE-22 — Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').
- Fix: Upgraded
jspdffrom3.0.4to4.0.0infrontend/package.jsonand regeneratedfrontend/package-lock.json, replacing the vulnerable path resolution logic with sanitized, bounded file access.
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
CVE-2025-68428 is a sharp reminder that critical vulnerabilities can hide in plain sight inside auto-generated lockfiles. A single dependency version — jspdf@3.0.4 — introduced a path traversal flaw that could have allowed an attacker to read arbitrary server-side files through the PDF generation pipeline. The fix is precise and low-risk: upgrading to jspdf@4.0.0, which closes the traversal vector without changing any valid behavior.
For developers building document generation features, the lesson is clear: treat every user-influenced value that touches the filesystem as untrusted, validate it at both the application layer and the library layer, and keep your dependency scanner running on every commit. A lockfile audit today can prevent a data breach tomorrow.