How Inherited Dependency Vulnerabilities Happen in Node.js and How to Fix It
The pnpm-lock.yaml file in this project quietly contained a ticking clock: tmp@0.2.5, a transitive dependency pulled in by tmp-promise@3.0.3, carried CVE-2026-44705 — a HIGH-severity vulnerability in temporary file handling. No direct code change introduced it. No developer consciously chose it. It arrived as invisible cargo inside another package, and it would have stayed invisible without automated scanning.
This post walks through exactly what happened, why it matters, and how a targeted pnpm override resolved it across three files.
The Vulnerability Explained
What Is CVE-2026-44705?
tmp is a widely-used Node.js library for creating temporary files and directories. Prior to version 0.2.6, it contained unsafe temporary file creation logic — the kind of flaw covered by CWE-377: Insecure Temporary File.
Insecure temp file creation typically involves one or more of the following weaknesses:
- Predictable file names that attackers can guess and pre-create as symlinks
- Race conditions (TOCTOU) between checking whether a temp path exists and actually creating the file
- Insufficient permission bits on created temp files, allowing other local users to read or write them
In the case of CVE-2026-44705 specifically, tmp versions before 0.2.6 did not adequately guard against these conditions, leaving any application that creates temporary files via this library open to local privilege escalation or information disclosure.
How It Arrived: The Transitive Dependency Chain
The project did not directly depend on tmp. It depended on tmp-promise@3.0.3, a promise-based wrapper around tmp. And tmp-promise@3.0.3 pulled in tmp@0.2.5 — the vulnerable version.
You can see this clearly in the lockfile snapshot before the fix:
# pnpm-lock.yaml (BEFORE)
tmp-promise@3.0.3:
resolution: {integrity: sha512-RwM7MoPojPxs...}
tmp@0.2.5:
resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/...}
engines: {node: '>=14.14'}
And in the snapshots section:
# pnpm-lock.yaml snapshots (BEFORE)
tmp-promise@3.0.3:
dependencies:
tmp: 0.2.5
tmp@0.2.5: {}
This is a classic transitive dependency vulnerability: the vulnerable package is two levels deep in the dependency graph, invisible to a developer scanning only their package.json.
Attack Scenario
Imagine this application runs on a shared Linux server or inside a CI/CD pipeline where multiple processes share a filesystem. A component of the application uses tmp-promise to create a temporary file for intermediate processing — perhaps staging output from the esbuild build step or writing a temporary config file.
With tmp@0.2.5:
- The application calls
tmp.file()ortmp.dir()to create a temp path. - An attacker process (running as a different user on the same system) predicts the temp file name based on the predictable naming scheme.
- The attacker pre-creates a symlink at that path pointing to a sensitive file (e.g.,
/etc/passwdor an SSH key). - When the application writes to the "temp file," it actually writes to the symlink target — overwriting a sensitive system file or leaking data.
This is a TOCTOU (Time-of-Check to Time-of-Use) race condition, and it's especially dangerous in automated build pipelines where temp files are created and destroyed rapidly.
The Fix
Strategy: pnpm Dependency Override
Since tmp is a transitive dependency (not a direct one), simply updating tmp-promise in package.json wouldn't help — tmp-promise@3.0.3 still resolves tmp@0.2.5. The correct fix is to use a pnpm override, which forces the entire dependency tree to use a specific version of a package regardless of what upstream packages request.
The fix added "tmp": "0.2.7" to the pnpm.overrides section in package.json:
Before:
"pnpm": {
"overrides": {
"sharp": "^0.34.5",
"@img/sharp-libvips-darwin-arm64": "1.2.4"
}
}
After:
"pnpm": {
"overrides": {
"sharp": "^0.34.5",
"@img/sharp-libvips-darwin-arm64": "1.2.4",
"tmp": "0.2.7"
}
}
This single addition tells pnpm: no matter who asks for tmp, give them 0.2.7.
The Lockfile Update
The pnpm-lock.yaml reflects the resolved change. The resolution hash changes from the 0.2.5 integrity value to the 0.2.7 value:
# pnpm-lock.yaml (AFTER)
- tmp@0.2.5:
- resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==}
+ tmp@0.2.7:
+ resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==}
engines: {node: '>=14.14'}
And the snapshot for tmp-promise now correctly resolves to the safe version:
# snapshots (AFTER)
tmp-promise@3.0.3:
dependencies:
- tmp: 0.2.5
+ tmp: 0.2.7
The dist/cli.js Update
The compiled dist/cli.js also embeds the package configuration, so it received the same override addition:
// dist/cli.js (AFTER)
var pnpm = {
overrides: {
sharp: "^0.34.5",
"@img/sharp-libvips-darwin-arm64": "1.2.4",
tmp: "0.2.7" // <-- added
},
...
}
This ensures that any tooling consuming the compiled CLI bundle also reflects the correct dependency policy.
Why 0.2.7 Instead of 0.2.6?
The PR title references 0.2.6 as the minimum safe version (the first version to address CVE-2026-44705), but the actual fix pins to 0.2.7 — a patch release that includes additional hardening on top of the CVE fix. Pinning to the latest patch version is the safer choice when the API surface is unchanged.
Prevention & Best Practices
1. Audit Transitive Dependencies Regularly
Your direct dependencies are only the tip of the iceberg. Use lockfile-aware scanners that traverse the full dependency graph:
# Trivy (detected this issue)
trivy fs --scanners vuln .
# npm audit (works with pnpm via compatibility layer)
pnpm audit
# Snyk
snyk test
2. Use Package Manager Overrides for Transitive Fixes
When a vulnerability lives in a transitive dependency, overrides are the correct tool:
- pnpm:
pnpm.overridesinpackage.json - npm:
overridesinpackage.json(npm 8.3+) - yarn:
resolutionsinpackage.json
3. Commit and Review Your Lockfile
The pnpm-lock.yaml file is security-critical. Always commit it, review changes to it in PRs, and treat unexpected version bumps as a potential supply chain concern.
4. Avoid Insecure Temp File Patterns in Your Own Code
If you write code that creates temporary files directly (without a library), follow these rules:
- Use
fs.mkstemp-equivalent APIs that atomically create files with restricted permissions - Never construct temp file paths by concatenating predictable strings
- Always use
O_EXCLflag when creating temp files to prevent TOCTOU races - Clean up temp files in
finallyblocks or use libraries that register cleanup handlers
5. Reference Standards
Key Takeaways
tmp@0.2.5is vulnerable; the safe floor is0.2.6, and0.2.7is the recommended pin — this specific version boundary is what CVE-2026-44705 is about.tmp-promise@3.0.3acts as a silent carrier — it resolves the vulnerabletmpversion without any warning in your direct dependency list.- A pnpm override in
package.jsonis the correct fix when you cannot update the direct dependent (tmp-promise) itself; it forces the safe version across the entire tree. - Lockfile integrity matters — the resolution hash in
pnpm-lock.yamlchanged fromsha512-voyz6MA...tosha512-e0votIpp..., providing a cryptographic guarantee that the correct package is installed. - Compiled artifacts like
dist/cli.jsalso embed dependency policy — forgetting to update them would leave the documented configuration inconsistent with the actual security posture.
How Orbis AppSec Detected This
- Source: The
pnpm-lock.yamllockfile, which resolvedtmp-promise@3.0.3's dependency totmp@0.2.5 - Sink: Any call site within the application that invokes
tmp.file()ortmp.dir()via thetmp-promisewrapper, creating temporary files with unsafe guarantees - Missing control: No version override existed to prevent pnpm from resolving the vulnerable
tmp@0.2.5version, and no minimum-version constraint was enforced on the transitive dependency - CWE: CWE-377 — Insecure Temporary File
- Fix: Added
"tmp": "0.2.7"to thepnpm.overridessection inpackage.jsonand updatedpnpm-lock.yamlanddist/cli.jsto reflect the pinned safe version
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-2026-44705 is a reminder that your application's security posture is only as strong as its deepest dependency. The tmp package vulnerability didn't arrive through a developer mistake or a bad architectural decision — it arrived silently, two levels deep in the dependency graph, carried by a perfectly reasonable library choice (tmp-promise).
The fix is surgical and non-breaking: a single pnpm override pins tmp to 0.2.7 across the entire tree, closing the vulnerability without touching any application logic. Three files changed, zero behavior changed, one CVE eliminated.
The broader lesson: lockfile-aware vulnerability scanning isn't optional. Tools like Trivy that read your pnpm-lock.yaml and trace the full resolution graph are the only reliable way to catch vulnerabilities like this before they reach production.