Back to Blog
critical SEVERITY8 min read

How Archive Path Traversal Happens in Node.js and How to Fix It

CVE-2026-53486 is a critical path traversal vulnerability in the Decompress library, where crafted archive entries can write files and symbolic links outside the intended extraction directory. This vulnerability was transitively introduced through `@vitest/browser` and related packages pinned at version 4.1.5, and was resolved by upgrading to 4.1.6 and 5.0.0-beta.3. Left unpatched, an attacker who controls an archive file processed by any downstream consumer of this dependency chain could overwr

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

Answer Summary

CVE-2026-53486 is a critical path traversal vulnerability (CWE-22) in the Decompress Node.js library, where maliciously crafted archive entries can extract files and symbolic links to locations outside the intended target directory. The vulnerability entered the Storybook codebase transitively through `@vitest/browser` 4.1.5 and its sibling packages. The fix upgrades `@vitest/browser`, `@vitest/browser-playwright`, `@vitest/coverage-istanbul`, `@vitest/coverage-v8`, and `vitest` from `^4.1.5` to `^4.1.6` (and the beta channel to `5.0.0-beta.3`) across `package.json` and `yarn.lock`, pulling in a patched version of Decompress that validates entry paths before writing to disk.

Vulnerability at a Glance

cweCWE-22
fixUpgrade `@vitest/browser` and related vitest packages from 4.1.5 → 4.1.6, which pulls in a patched Decompress transitive dependency
riskArbitrary file write and symbolic link creation outside the extraction target directory
languageJavaScript / Node.js
root causeDecompress did not sanitize archive entry paths containing `../` sequences or absolute paths before writing extracted content to disk
vulnerabilityArchive Path Traversal (Zip Slip)

How Archive Path Traversal Happens in Node.js and How to Fix It

The Hidden Risk in Your Test Infrastructure

Test tooling rarely gets the same security scrutiny as production application code — but it lives in the same dependency graph, and its transitive dependencies can carry critical vulnerabilities into your project. That is exactly what happened here.

Trivy's static analysis scanner flagged yarn.lock in the Storybook monorepo with CVE-2026-53486, a critical path traversal vulnerability in the decompress library. The vulnerability did not arrive through a direct dependency. It arrived as a transitive dependency of @vitest/browser 4.1.5 — a package used for browser-based test execution. The fix was a targeted upgrade of five vitest-family packages across three package.json files.


The Vulnerability Explained

What Is Archive Path Traversal (Zip Slip)?

The decompress library is a popular Node.js utility for extracting .zip, .tar, .tar.gz, and other archive formats. Its job is simple: read entries from an archive and write them to a target directory.

The vulnerability arises when the library does not validate whether an archive entry's path, after resolution, still points inside the target directory. An attacker who controls the archive content can craft entries like:

../../../../etc/cron.d/malicious
../../../home/user/.ssh/authorized_keys

Or even absolute paths:

/etc/passwd
/usr/local/bin/node

When decompress resolves these paths and writes the extracted bytes, the resulting file lands outside the intended extraction directory. This class of vulnerability is sometimes called Zip Slip (named after a coordinated disclosure by Snyk in 2018), and it affects any extraction library that fails to canonicalize output paths.

CVE-2026-53486 is a new instance of this pattern in a version of decompress that was pulled in transitively by @vitest/browser 4.1.5.

The Vulnerable Dependency Chain

The vulnerable path in the Storybook repository looked like this:

@vitest/browser@4.1.5
  └── (transitive) decompress@<patched-version>
        └──  No path boundary check before file.write()

Because decompress did not verify that the resolved output path was contained within the target directory, any code path that called decompress() on attacker-influenced archive data was exploitable.

Concrete Attack Scenario

Consider a CI/CD pipeline or a developer machine running Storybook's test suite. If the test runner fetches a browser binary, a fixture archive, or any compressed asset from an untrusted or compromised source, and that asset is processed by decompress via the @vitest/browser internals, an attacker-controlled entry such as:

../../../.npmrc

…could overwrite the developer's npm configuration file, injecting a malicious registry URL. A more severe variant could target:

../../../../usr/local/lib/node_modules/some-global-tool/index.js

…overwriting a globally installed tool's entry point with arbitrary code that executes the next time that tool runs.

Even in environments where the extraction target is sandboxed, symbolic link entries (e.g., a .tar entry of type symlink pointing to /etc) can be used to escape the sandbox in a second extraction step.


The Fix

What Changed

The fix upgrades five vitest-family packages from ^4.1.5 to ^4.1.6 (and the beta channel to 5.0.0-beta.3) across three package.json files. Version 4.1.6 of @vitest/browser and its sibling packages resolve to a patched version of decompress that validates entry paths before writing.

code/addons/vitest/package.json

- "@vitest/browser-playwright": "^4.1.5",
- "@vitest/runner": "^4.1.5",
+ "@vitest/browser-playwright": "^4.1.6",
+ "@vitest/runner": "^4.1.6",

- "vitest": "^4.1.5"
+ "vitest": "^4.1.6"

code/package.json

- "@vitest/browser": "^4.1.5",
- "@vitest/browser-playwright": "^4.1.5",
- "@vitest/coverage-istanbul": "^4.1.5",
- "@vitest/coverage-v8": "^4.1.5",
+ "@vitest/browser": "^4.1.6",
+ "@vitest/browser-playwright": "^4.1.6",
+ "@vitest/coverage-istanbul": "^4.1.6",
+ "@vitest/coverage-v8": "^4.1.6",

- "vitest": "^4.1.5"
+ "vitest": "^4.1.6"

package.json (root)

- "vitest": "^4.1.5"
+ "vitest": "^4.1.6"

Why All Five Packages?

The vitest ecosystem packages (@vitest/browser, @vitest/browser-playwright, @vitest/coverage-istanbul, @vitest/coverage-v8, @vitest/runner) are versioned in lockstep. Upgrading only one while leaving the others at 4.1.5 would still allow yarn to resolve the old, vulnerable transitive dependency through whichever 4.1.5 package remained. Upgrading all five simultaneously ensures that yarn.lock resolves a single consistent — and patched — closure of transitive dependencies.

How the Patched decompress Fixes the Problem

The corrected extraction logic in the patched decompress version performs a path canonicalization check before any file write or symlink creation:

// Conceptual representation of the fix in decompress
const targetDir = path.resolve(outputDirectory);
const outputPath = path.resolve(targetDir, entryPath);

if (!outputPath.startsWith(targetDir + path.sep)) {
  throw new Error(`Path traversal detected: ${entryPath}`);
}

// Safe to write
fs.writeFileSync(outputPath, entryData);

By calling path.resolve() on both the target directory and the combined target+entry path, and then asserting that the result starts with the target directory prefix (including a trailing separator to prevent prefix-match bypasses like /tmp/safe-dir-evil), the library ensures that no entry — regardless of how many ../ components it contains — can escape the intended extraction root.


Prevention & Best Practices

1. Audit Transitive Dependencies, Not Just Direct Ones

This vulnerability was not in a package listed directly in dependencies or devDependencies. It was two or more levels deep in the dependency tree. Tools like Trivy, npm audit, Snyk, and OWASP Dependency-Check can surface vulnerable transitive packages that manual review would miss.

2. Pin or Range-Constrain Lockfiles

The use of ^4.1.5 (caret ranges) in package.json combined with a committed yarn.lock is good practice — it means the lockfile captures the exact resolved version. However, the lockfile must be regenerated after upgrading the range bounds. Always commit the updated yarn.lock alongside package.json changes.

3. Validate Extraction Paths in Your Own Code

If you write code that extracts archives (using decompress, adm-zip, node-tar, or any other library), always validate the resolved output path:

const path = require('path');

function safeExtractPath(targetDir, entryName) {
  const resolved = path.resolve(targetDir, entryName);
  const normalized = path.normalize(resolved);
  if (!normalized.startsWith(path.resolve(targetDir) + path.sep)) {
    throw new Error(`Unsafe archive entry: ${entryName}`);
  }
  return normalized;
}

4. Apply the Principle of Least Privilege to Extraction Processes

Run archive extraction in a process with the minimum filesystem permissions necessary. If the extractor only needs to write to /tmp/extracted/, it should not have write access to /etc/ or ~/.ssh/. Container sandboxing and filesystem namespaces can limit the blast radius of a successful Zip Slip attack.

5. Reference Standards

  • CWE-22: Improper Limitation of a Pathname to a Restricted Directory ("Path Traversal")
  • OWASP Path Traversal: https://owasp.org/www-community/attacks/Path_Traversal
  • Zip Slip Vulnerability: Originally disclosed by Snyk; affects dozens of archive libraries across multiple languages

Key Takeaways

  • Test-only dependencies are not automatically safe: @vitest/browser is a devDependency, but its transitive dependency decompress carried a critical CVE that affected any developer or CI runner processing archives through that package.
  • yarn.lock is your ground truth for transitive vulnerabilities: Trivy flagged yarn.lock specifically because that file captures the exact resolved versions of all transitive packages — including the vulnerable decompress build.
  • Upgrading one package in a lockstep ecosystem is not enough: All five @vitest/* packages needed simultaneous bumps from 4.1.5 → 4.1.6 to ensure yarn resolved a consistent, patched dependency closure.
  • Path traversal in archive extraction is a solved problem: The fix is a two-line path.resolve() + startsWith() check. Any extraction library that omits this check should be considered unsafe regardless of its popularity.
  • Automated scanning caught what code review would not: No human reviewer auditing a package.json diff for a version bump would have traced the decompress transitive dependency path. Static analysis tooling is essential for this class of vulnerability.

How Orbis AppSec Detected This

  • Source: Archive entry paths within compressed files processed by the decompress transitive dependency, reachable through @vitest/browser@4.1.5 internals
  • Sink: decompress's file-write routine, which accepted unsanitized entry paths and resolved them relative to the process working directory rather than the declared target directory
  • Missing control: No path.resolve() canonicalization or startsWith(targetDir) boundary check before writing extracted file content or creating symbolic links
  • CWE: CWE-22 — Improper Limitation of a Pathname to a Restricted Directory ("Path Traversal")
  • Fix: Upgraded @vitest/browser, @vitest/browser-playwright, @vitest/coverage-istanbul, @vitest/coverage-v8, and vitest from ^4.1.5 to ^4.1.6 across all three package.json files, pulling in a patched decompress transitive dependency that validates entry paths before extraction.

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-53486 is a sharp reminder that the security boundary of your application extends all the way to the leaves of your dependency tree — including test infrastructure. A critical path traversal vulnerability in decompress, surfaced through the @vitest/browser 4.1.5 dependency chain, could have allowed an attacker to write arbitrary files outside the intended extraction directory on any developer machine or CI runner processing a crafted archive.

The fix was surgical: five version bumps across three package.json files, regenerating yarn.lock to pull in a patched transitive dependency. But the detection required automated tooling — specifically Trivy scanning yarn.lock and tracing the transitive dependency graph back to the vulnerable decompress version.

Archive extraction is one of the oldest classes of path traversal vulnerabilities, and the mitigation has been well understood for years: canonicalize paths, check boundaries, reject traversals. When choosing or auditing archive libraries in your Node.js projects, verify that the library performs this check before you depend on it.


References

Frequently Asked Questions

What is archive path traversal (Zip Slip)?

It is a vulnerability where a maliciously crafted archive contains entry paths with `../` sequences or absolute paths, causing extraction to write files outside the intended target directory — potentially overwriting system files or injecting malicious code.

How do you prevent path traversal in Node.js archive extraction?

Always canonicalize the resolved output path and verify it starts with the intended target directory prefix before writing any extracted file or creating any symbolic link.

What CWE is archive path traversal?

CWE-22 (Improper Limitation of a Pathname to a Restricted Directory — "Path Traversal").

Is restricting file extensions enough to prevent Zip Slip?

No. Extension filtering does not prevent path traversal because the danger is in the directory component of the entry path, not the file extension.

Can static analysis detect archive path traversal?

Yes. Tools like Trivy (which flagged this issue as CVE-2026-53486) and Semgrep rules targeting unsafe archive extraction can detect vulnerable dependency versions and unsafe extraction patterns in source code.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #35530

Related Articles

high

How Path Traversal happens in Python Flask apps and how to fix it

A path traversal vulnerability in `WAVE2SCORE/app.py` allowed attackers to supply a crafted file path that escaped the application's intended working directory, potentially enabling access to arbitrary files on the server. The fix resolves the absolute path and validates it against the expected `WORK_DIR` boundary before any processing occurs. This kind of boundary check is a critical safeguard in any application that processes user-supplied file paths.

high

How Arbitrary File Read happens in Python LangSmith SDK and how to fix it

A high-severity arbitrary server-side file read vulnerability (GHSA-f4xh-w4cj-qxq8) was discovered in LangSmith SDK's `TracingMiddleware`, affecting versions prior to 0.8.18. An attacker able to influence tracing requests could potentially read arbitrary files from the server's filesystem. Upgrading from version 0.8.15 to 0.8.18 in `poetry.lock` and `pyproject.toml` closes the attack surface entirely.

critical

How Command Injection happens in Python PopClip Extensions and how to fix it

A critical command injection vulnerability was discovered in `contrib/Klipz.popclipext/Klipz.py`, where user-controlled clipboard content was concatenated directly into shell commands executed via `osascript`. The fix replaces unsafe string concatenation with `subprocess` and proper argument lists, and replaces the unsafe `pickle` serialization with `json` to eliminate a secondary deserialization risk. Together, these changes close two distinct attack surfaces in a single file.

high

How Path Traversal happens in PostCSS Source Map Auto-Loading and how to fix it

A high-severity path traversal vulnerability in PostCSS versions before 8.5.18 allowed attackers to manipulate `sourceMappingURL` directives to load arbitrary `.map` files from the filesystem, potentially disclosing sensitive source code and build metadata. The fix upgrades PostCSS from 8.5.15 to 8.5.18 in `console/web/package-lock.json`, closing the path traversal vector in the source map auto-loading feature. This change protects applications that process untrusted CSS input through their Post

critical

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

A path traversal vulnerability in `skills/baoyu-design/agents/import-design-system.mjs` allowed attackers to escape the intended design system directory by supplying absolute paths, bypassing a guard that only checked for `..` prefixes. The fix adds an `isAbsolute()` check alongside the existing relative-path guard, closing the bypass with a single targeted change. This matters because the `dsDir` argument is user-controlled, meaning any caller of the script could redirect file operations to sen

critical

How Command Injection happens in Node.js shell-quote and how to fix it

CVE-2026-9277 is a critical command injection vulnerability in the `shell-quote` npm package (versions prior to 1.8.4) caused by unescaped line terminators that allow attackers to inject and execute arbitrary shell commands. The fix pins `shell-quote` to `>=1.8.4` via a `pnpm.overrides` entry, ensuring every transitive consumer in the dependency tree receives the patched version. Any Node.js project that processes user-influenced input through `shell-quote` and has not yet upgraded is at risk of