Back to Blog
high SEVERITY7 min read

How Path Traversal happens in Node.js temporary file creation and how to fix it

CVE-2026-44705 is a high-severity path traversal vulnerability in the Node.js `tmp` package where unsanitized `prefix` and `postfix` options allow attackers to escape the intended temporary directory. Three separate nested copies of `tmp` — versions `0.0.28` and `0.2.7` pinned under `can-symlink`, `broccoli`, and `ember-template-recast` — were removed from `package-lock.json` and replaced by a single patched `0.2.6` resolution. The fix eliminates the directory-escape attack surface while leaving

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

Answer Summary

CVE-2026-44705 is a high-severity path traversal vulnerability (CWE-22) in the Node.js `tmp` package. Versions prior to 0.2.6 pass the caller-supplied `prefix` and `postfix` options directly into the file-system path without sanitizing `../` sequences, allowing an attacker who controls those values to write files outside the designated temporary directory. The fix is to upgrade `tmp` to 0.2.6, which validates and strips path-separator characters from both options before constructing the final path. In this repository, three nested package-lock.json entries pinning `tmp@0.0.28` and `tmp@0.2.7` were removed so the dependency tree resolves to the patched release.

Vulnerability at a Glance

cweCWE-22
fixRemove all nested tmp pins below 0.2.6 from package-lock.json so the dependency tree resolves to the patched release
riskAttacker-controlled prefix or postfix writes files outside /tmp, potentially overwriting sensitive files or planting malicious code
languageJavaScript / Node.js
root causetmp package concatenated caller-supplied strings into a file path without stripping ../ sequences
vulnerabilityPath Traversal via unsanitized tmp prefix/postfix

Introduction

The package-lock.json file in this repository quietly harbored three separate copies of the tmp package — one under node_modules/can-symlink/node_modules/tmp at version 0.0.28, one under node_modules/broccoli/node_modules/tmp at version 0.2.7, and one under node_modules/ember-template-recast/node_modules/tmp also at 0.2.7. Each of those nested pins resolved to a release of tmp that passes caller-supplied prefix and postfix strings directly into a file-system path without stripping ../ sequences — a classic path traversal condition now tracked as CVE-2026-44705 with a HIGH severity rating.

Because tmp is used to create temporary files and directories during build and test tooling (Broccoli, can-symlink, ember-template-recast all depend on it), any code path that lets external input influence the prefix or postfix option flows straight into an unsafe file-path construction routine.


The Vulnerability Explained

What tmp does and where it goes wrong

The tmp package exposes a simple API:

// Typical usage — looks harmless
const tmp = require('tmp');
tmp.file({ prefix: userSuppliedPrefix, postfix: userSuppliedPostfix }, callback);

Internally, vulnerable versions construct the final path by doing something equivalent to:

// Simplified pseudocode from tmp < 0.2.6
const name = prefix + generateRandomChars() + postfix;
const fullPath = path.join(os.tmpdir(), name);

The critical flaw is that prefix and postfix are never sanitized. If an attacker controls either value and injects ../../etc/ as a prefix, the resulting path escapes the temporary directory entirely:

os.tmpdir()  →  /tmp
prefix       →  ../../etc/passwd_
random       →  xK3mZ9
postfix      →  .bak

fullPath     →  /tmp/../../etc/passwd_xK3mZ9.bak
             →  /etc/passwd_xK3mZ9.bak   (after resolution)

Why 0.0.28 is especially dangerous

The can-symlink dependency pinned tmp@0.0.28 — an extremely old release that predates any of the modern input-validation work in the 0.2.x branch. Version 0.0.28 also pulls in os-tmpdir@~1.0.1 as a polyfill, adding another legacy surface area. The engines field in that entry ("node": ">=0.4.0") signals just how ancient this code is:

// Removed from package-lock.json by this fix
"node_modules/can-symlink/node_modules/tmp": {
  "version": "0.0.28",
  "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.28.tgz",
  "dependencies": {
    "os-tmpdir": "~1.0.1"
  },
  "engines": {
    "node": ">=0.4.0"
  }
}

Attack scenario

Consider a build script that accepts a user-defined artifact name and passes it as the prefix to a tmp.dir() call:

// Hypothetical build tooling using the vulnerable tmp
const tmp = require('tmp');
const artifactName = req.body.name; // attacker-controlled

tmp.dir({ prefix: artifactName + '-' }, (err, dirPath) => {
  fs.writeFileSync(path.join(dirPath, 'output.js'), compiledCode);
});

An attacker submits name = "../../home/deploy/.ssh/authorized_keys_". The temporary directory is now created at (or near) /home/deploy/.ssh/, and output.js — potentially containing attacker-chosen content — is written there. On a CI/CD server this could mean planting a malicious script that executes on the next deployment.


The Fix

What changed in package-lock.json

The pull request removes all three nested tmp overrides that were forcing sub-dependency trees to resolve to vulnerable versions:

Removed entry Version Parent package
node_modules/can-symlink/node_modules/tmp 0.0.28 can-symlink
node_modules/broccoli/node_modules/tmp 0.2.7 broccoli
node_modules/ember-template-recast/node_modules/tmp 0.2.7 ember-template-recast

Before (three separate vulnerable pins):

"node_modules/can-symlink/node_modules/tmp": {
  "version": "0.0.28",
  ...
  "dependencies": { "os-tmpdir": "~1.0.1" },
  "engines": { "node": ">=0.4.0" }
},
"node_modules/broccoli/node_modules/tmp": {
  "version": "0.2.7",
  ...
  "engines": { "node": ">=14.14" }
},
"node_modules/ember-template-recast/node_modules/tmp": {
  "version": "0.2.7",
  ...
  "engines": { "node": ">=14.14" }
}

After: all three blocks are deleted, so npm resolves a single hoisted tmp@0.2.6 for the entire dependency tree.

Why 0.2.6 is safe

tmp@0.2.6 introduces explicit sanitization of the prefix and postfix options before they are used in path construction. Path-separator characters and .. sequences are rejected or stripped, so even if an attacker supplies ../../etc/ as a prefix, the library either throws an error or reduces it to a safe string before the path.join() call.

The fix is minimal and non-breaking: valid prefixes (alphanumeric strings, hyphens, underscores) pass through unchanged, so all existing callers in broccoli, can-symlink, and ember-template-recast continue to work exactly as before.


Key Takeaways

  • tmp@0.0.28 (used by can-symlink) is over a decade old and lacks any input validation — if it appears in your lock file, treat it as an immediate risk.
  • Nested package-lock.json pins can silently keep vulnerable versions alive even after you upgrade the top-level dependency; always check npm ls tmp to see every resolved copy.
  • The prefix and postfix options in tmp are a direct path-construction sink — any user-controlled data flowing into them without sanitization is a textbook CWE-22.
  • Removing the nested overrides (not just bumping the top-level version) was the correct fix here, because npm would have continued to hoist the old pinned versions otherwise.
  • Trivy's filesystem scan caught what a simple npm audit might miss — scanner diversity matters for nested-dependency vulnerabilities.

How Orbis AppSec Detected This

  • Source: Caller-supplied prefix and postfix option values passed to tmp.file() / tmp.dir() — values that can be influenced by external input such as build configuration, CLI arguments, or request parameters.
  • Sink: Internal path-join call inside tmp (versions 0.0.28 and 0.2.7) that concatenates the unsanitized prefix/postfix with os.tmpdir() to produce the final file-system path.
  • Missing control: No stripping or rejection of ../, /, or \ characters in the prefix / postfix strings before path construction.
  • CWE: CWE-22 — Improper Limitation of a Pathname to a Restricted Directory (Path Traversal).
  • Fix: Removed all three nested tmp entries (0.0.28 under can-symlink, 0.2.7 under broccoli, 0.2.7 under ember-template-recast) from package-lock.json so the entire dependency tree resolves to the patched tmp@0.2.6.

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 sharp reminder that dependency hygiene is not a one-time task. Three separate copies of the tmp package — each pinned to a vulnerable version deep inside the node_modules tree — were silently exposing this application to a directory-escape attack. The unsanitized prefix and postfix options in old tmp releases are a direct path from attacker-controlled input to arbitrary file-system writes, a combination that can turn a build tool into a foothold for privilege escalation or code injection on CI/CD infrastructure.

The fix is clean and surgical: remove the nested pins, let npm resolve a single tmp@0.2.6, and the attack surface disappears without changing any application behavior. Pair that with regular npm audit, Trivy filesystem scans, and a root-level overrides policy, and you'll catch the next one before it ships.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #168

Related Articles

critical

How path traversal happens in PHP virtual filesystem adapters and how to fix it

A critical path traversal flaw in `VirtualAdapter.php`'s `resolveMount()` method allowed attackers to escape mounted directory boundaries using sequences like `../../../etc/passwd`. The fix introduces `PathPolicy::normalizeRelative()` to sanitize the remaining path segment before it ever reaches the underlying storage adapter.

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.

high

How Trust-Prefix Bypass via Path Traversal Happens in Python Copier and How to Fix It

CVE-2026-53951 is a high-severity path traversal vulnerability in Copier 9.15.0 that allowed attackers to bypass trust-prefix checks and execute tasks without user confirmation. Upgrading to Copier 9.15.2 eliminates this attack vector by properly validating file paths before task execution.

critical

How Path Traversal in basic-ftp Leads to File Overwrite Attacks and How to Fix It

CVE-2026-27699 is a critical path traversal vulnerability in basic-ftp versions before 5.3.1 that allows attackers to overwrite arbitrary files on the system by crafting malicious file paths. This vulnerability was fixed by upgrading basic-ftp and enforcing strict version constraints across dependent packages. Understanding this attack and its mitigation is essential for developers using FTP libraries in production environments.

critical

How Command Injection Vulnerabilities Happen in Python Subprocess Calls and How to Fix Them

A critical command injection vulnerability was discovered in `src/unused/server/fft.py` where external binaries like `oggenc` and `cocoa_text` were executed with file path parameters that could be manipulated by user input. Although `shell=False` was used, the lack of input validation allowed attackers to potentially trigger processing of arbitrary files or cause denial of service. This fix implements proper path validation to prevent exploitation.

critical

How Path Traversal happens in Node.js Express servers and how to fix it

A path traversal vulnerability in `src/server.js` allowed attackers to escape the intended wiki directory by sending encoded traversal sequences through the `/api/pages/:slug(*)` wildcard endpoint. The flawed `startsWith` boundary check could be bypassed after `decodeURIComponent` processing, potentially exposing arbitrary files on the server. The fix replaces the inline filesystem logic with a dedicated `readWikiPage()` function that enforces proper path validation.