Back to Blog
high SEVERITY7 min read

How Inherited Dependency Vulnerabilities Happen in Node.js and how to fix it

A vulnerability in the `tmp` Node.js package (CVE-2026-44705) was discovered lurking as a transitive dependency via `tmp-promise@3.0.3`, leaving applications exposed to unsafe temporary file handling. The fix pins `tmp` to version `0.2.7` using a pnpm override across `package.json`, `dist/cli.js`, and `pnpm-lock.yaml`, eliminating the vulnerable code path without affecting any valid application behavior.

O
By Orbis AppSec
Published August 26, 2026Reviewed August 26, 2026

Answer Summary

CVE-2026-44705 is a HIGH-severity vulnerability in the `tmp` Node.js package (versions prior to 0.2.6) that allows unsafe handling of temporary files and directories, potentially enabling symlink attacks or insecure temp file creation. The vulnerability surfaces as a transitive dependency through `tmp-promise@3.0.3`. The fix, aligned with CWE-377 (Insecure Temporary File), is to override the `tmp` dependency to version `0.2.7` in `package.json` and `pnpm-lock.yaml`, preventing the vulnerable version from being resolved anywhere in the dependency tree.

Vulnerability at a Glance

cweCWE-377
fixPin `tmp` to `0.2.7` via a pnpm override in `package.json` and `pnpm-lock.yaml`
riskAttackers may exploit unsafe temp file creation to perform symlink attacks, race conditions, or information disclosure
languageJavaScript / Node.js
root cause`tmp@0.2.5` (pulled in transitively by `tmp-promise@3.0.3`) contained unsafe temporary file handling logic
vulnerabilityInsecure Temporary File Creation (CVE-2026-44705)

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:

  1. The application calls tmp.file() or tmp.dir() to create a temp path.
  2. An attacker process (running as a different user on the same system) predicts the temp file name based on the predictable naming scheme.
  3. The attacker pre-creates a symlink at that path pointing to a sensitive file (e.g., /etc/passwd or an SSH key).
  4. 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.


Key Takeaways

  • tmp@0.2.5 is vulnerable; the safe floor is 0.2.6, and 0.2.7 is the recommended pin — this specific version boundary is what CVE-2026-44705 is about.
  • tmp-promise@3.0.3 acts as a silent carrier — it resolves the vulnerable tmp version without any warning in your direct dependency list.
  • A pnpm override in package.json is 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.yaml changed from sha512-voyz6MA... to sha512-e0votIpp..., providing a cryptographic guarantee that the correct package is installed.
  • Compiled artifacts like dist/cli.js also 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.yaml lockfile, which resolved tmp-promise@3.0.3's dependency to tmp@0.2.5
  • Sink: Any call site within the application that invokes tmp.file() or tmp.dir() via the tmp-promise wrapper, creating temporary files with unsafe guarantees
  • Missing control: No version override existed to prevent pnpm from resolving the vulnerable tmp@0.2.5 version, and no minimum-version constraint was enforced on the transitive dependency
  • CWE: CWE-377 — Insecure Temporary File
  • Fix: Added "tmp": "0.2.7" to the pnpm.overrides section in package.json and updated pnpm-lock.yaml and dist/cli.js to 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.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1340

Related Articles

critical

deleteNestedProperty Prototype Pollution via Dot-Notation Path

The `deleteNestedProperty` function in propertyUtils.ts allowed attackers to manipulate JavaScript object prototypes by passing specially crafted dot-notation paths like `__proto__.polluted`. A fix now blocks dangerous keys before processing, preventing prototype pollution attacks that could affect all objects in the application.

high

How Denial of Service via Infinite Loop Happens in JavaScript Dependencies and How to Fix It

CVE-2026-67213 is a high-severity denial of service vulnerability in nanoid before version 5.1.6 that triggers an infinite loop during random ID generation when processing specially crafted input. We upgraded nanoid across the entire dependency tree to patch this flaw and prevent attackers from freezing application threads. This fix ensures that ID generation remains resilient even when handling adversarial input patterns.

high

How Sensitive Data Exposure happens in Zotero plugins and how to fix it

A high-severity data exposure vulnerability in `Zotero.ts` automatically transmitted complete document metadata—including private notes, attachment paths, and tags—to external LLM services without user consent. The fix replaces broad `item.toJSON()` serialization with explicit field selection, sending only essential bibliographic data.

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.

high

How Path Traversal Happens in TensorFlow's Data Service and How to Fix It

TensorFlow's data service dispatcher validated dataset IDs against forward-slash traversal attacks but overlooked backslash characters on non-Windows platforms, allowing attackers to escape the root directory. A targeted fix adds explicit backslash validation across all platforms, closing a high-severity path traversal vulnerability in the snapshot management system.

critical

How Unbounded WebSocket Message Handling Causes Resource Exhaustion in Node.js and How to Fix It

The WebSocketCrossServerAdapter class in a popular Node.js WebSocket library lacked any rate limiting on inbound messages, allowing attackers to flood Redis nodes and WebSocket servers with high-volume traffic. The fix introduces a configurable `rateLimit` option that caps messages per connection per second, preventing resource exhaustion while preserving legitimate functionality.