Back to Blog
critical SEVERITY6 min read

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

A critical zip-slip vulnerability (CVE-2026-53486) in the `@xhmikosr/decompress` package allowed crafted archives to write files outside the intended extraction directory, enabling arbitrary file read/write on the host. The fix upgrades `@xhmikosr/decompress` from 5.0.0 to 10.2.1/11.1.3 and its dependency `@xhmikosr/bin-wrapper` from ^5.0.0 to ^13.2.0, closing the path-sanitization gap in the underlying extractors.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published August 31, 2026•Reviewed August 31, 2026

Answer Summary

CVE-2026-53486 is a path traversal (zip-slip, CWE-22) vulnerability in the Node.js package `@xhmikosr/decompress` (v5.0.0), where crafted archive entries with `../` sequences or absolute paths in their filenames could escape the target extraction directory during decompression, leading to arbitrary file read/write. The fix is to upgrade the dependency to `@xhmikosr/decompress@10.2.1`/`11.1.3` (and `@xhmikosr/bin-wrapper` to `^13.2.0`), which added proper entry-path validation before writing extracted files to disk.

Vulnerability at a Glance

cweCWE-22
fixUpgrade `@xhmikosr/decompress` to `10.2.1`/`11.1.3` and `@xhmikosr/bin-wrapper` to `^13.2.0` in `package.json`/`bun.lock`
riskArbitrary file read/write outside the intended extraction directory via a crafted archive
languageJavaScript / Node.js
root cause`@xhmikosr/decompress@5.0.0` did not validate extracted entry paths for `../` traversal sequences or absolute paths before writing files
vulnerabilityPath Traversal / Zip Slip in archive extraction

Introduction

The @xhmikosr/decompress package is a common building block in Node.js tooling used to unpack .zip, .tar, .tar.gz, and other archive formats — often as part of build scripts, CLI tools, or binary download helpers. In this repository, it's pulled in transitively through @xhmikosr/bin-wrapper, which several workspace packages (packages/binaries/celestia, packages/binaries/near-sandbox, packages/binaries/ord, packages/binaries/solana-node, and others) depend on to download and extract pre-built binaries during installation or build.

The problem: version 5.0.0 of @xhmikosr/decompress, tracked in bun.lock and pinned via @xhmikosr/bin-wrapper@^5.0.0, is vulnerable to CVE-2026-53486 — a critical zip-slip vulnerability that allows a maliciously crafted archive to write (or read) files outside the directory the code intended to extract into. If any workflow in this repo extracts an archive from a source that isn't fully trusted (a downloaded binary release, a CI artifact, a third-party mirror), a single crafted archive entry could overwrite files far outside the expected output folder.

Trivy's dependency scanner flagged this exact pattern against bun.lock, and while the assessment notes it as "present in dependency tree, not confirmed reachable," that's precisely the kind of latent risk that turns into a real incident the moment a build script or binary-fetch path starts consuming an untrusted archive.

The Vulnerability Explained

Archive formats like zip and tar store a relative path for every file entry — that's how you get a directory structure back out when you extract. A well-behaved archive entry looks like:

readme.txt
bin/celestia
lib/config.json

A malicious archive entry, however, can specify a path designed to escape the extraction directory:

../../../../etc/cron.d/malicious-job
../../.ssh/authorized_keys

Older versions of extraction libraries — including @xhmikosr/decompress@5.0.0 — historically did not fully canonicalize and validate each entry's destination path before writing it to disk. Internally, code in that vintage of the extractor effectively does something equivalent to:

// Simplified representation of the vulnerable pattern
const outputPath = path.join(destDir, entry.path); // entry.path is attacker-controlled
fs.writeFileSync(outputPath, entry.data);

Because path.join(destDir, entry.path) doesn't stop entry.path from containing .. segments (or an absolute path on some platforms), the resulting outputPath can resolve to a location completely outside destDir. This is the classic "zip slip" pattern, and it's exactly the class of bug CVE-2026-53486 targets in decompress.

Attack scenario specific to this codebase: The packages/binaries/* workspaces (celestia, near-sandbox, ord, solana-node) use @xhmikosr/bin-wrapper, which under the hood downloads a binary release archive and hands it to decompress for extraction. If an attacker can influence the archive source — a compromised mirror, a man-in-the-middle on an unpinned download URL, or a poisoned upstream release — they could ship a .tar.gz where one entry's path is ../../../../home/deploy/.bashrc or ../../../etc/systemd/system/backdoor.service. On extraction, that entry gets written outside the intended packages/binaries/<name>/bin/ directory, potentially achieving arbitrary file write and, depending on what gets overwritten, code execution or persistence on the build/runtime host.

The Fix

The PR resolves this by bumping the vulnerable dependency chain in package.json and regenerating bun.lock:

Before:

       "dependencies": {
         "@effectstream/binary-checksum": "workspace:*",
-        "@xhmikosr/bin-wrapper": "^5.0.0",
+        "@xhmikosr/bin-wrapper": "^13.2.0",
       },

This change is applied consistently across every workspace that pulled in the vulnerable version:

  • packages/binaries/celestia
  • packages/binaries/near-sandbox
  • packages/binaries/ord
  • packages/binaries/solana-node

@xhmikosr/bin-wrapper@^13.2.0 itself depends on a patched @xhmikosr/decompress (upgraded to 10.2.1/11.1.3 in the lockfile), which includes proper entry-path validation before extraction — rejecting or normalizing entries that would resolve outside the destination directory. The lockfile diff also shows a related transitive bump, @sindresorhus/is from 4.6.0 to 5.6.0, pulled in as part of the dependency tree update for the newer bin-wrapper/decompress chain.

Why this is the right fix, and why it's low-risk: This is a pure dependency version bump, not a behavioral rewrite of extraction logic in this repo's own code. The vulnerable code lived entirely inside @xhmikosr/decompress, so the safest and most maintainable fix is to consume the upstream patch rather than hand-roll path sanitization around a third-party extractor. Because the change only tightens how malicious archive entries are handled — legitimate archives with well-formed relative paths extract exactly as before — normal build and install workflows are unaffected.

Key Takeaways

  • @xhmikosr/decompress@5.0.0, used transitively via @xhmikosr/bin-wrapper@^5.0.0, was vulnerable to zip-slip path traversal (CVE-2026-53486) that could enable arbitrary file read/write during archive extraction.
  • Four workspaces — celestia, near-sandbox, ord, and solana-node — pulled in the vulnerable chain through their binary-download tooling and needed the bump to @xhmikosr/bin-wrapper@^13.2.0.
  • The fix is a lockfile/manifest-only change; no application code needed to be rewritten because the flaw lived entirely in the third-party extraction library.
  • Even when a scanner marks a dependency as "not confirmed reachable," it's still worth patching promptly — archive extraction paths are exactly the kind of code that gets exercised unexpectedly (CI, install scripts, binary fetchers).
  • Treat any library that writes files based on data inside an untrusted input (archives, uploads, templates) as a high-priority target for version tracking and CVE monitoring.

How Orbis AppSec Detected This

  • Source: Archive entry filenames inside a downloaded binary release (.zip/.tar.gz) fetched by @xhmikosr/bin-wrapper in packages/binaries/{celestia,near-sandbox,ord,solana-node}.
  • Sink: Internal file-write call inside @xhmikosr/decompress@5.0.0's extraction routine, which joined the untrusted entry path with the destination directory without full containment validation.
  • Missing control: No canonicalization/containment check to ensure the resolved extraction path stayed within the intended destination directory (no ..//absolute-path rejection).
  • CWE: CWE-22 (Improper Limitation of a Pathname to a Restricted Directory / Path Traversal).
  • Fix: Upgraded @xhmikosr/bin-wrapper to ^13.2.0 (pulling in patched @xhmikosr/decompress@10.2.1/11.1.3) in package.json and regenerated bun.lock accordingly.

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 textbook zip-slip vulnerability: an archive extraction library that trusted entry paths just enough to let a crafted ../ sequence escape the intended output directory, opening the door to arbitrary file read/write. Because @xhmikosr/decompress@5.0.0 sat several major versions behind and was consumed transitively across four binary-handling workspaces, the blast radius wasn't obvious from a casual read of package.json — it took dependency-tree analysis to surface it. The fix here is simple and safe: bump @xhmikosr/bin-wrapper to ^13.2.0 and let the patched @xhmikosr/decompress (10.2.1/11.1.3) enforce proper path containment. The broader lesson is to keep archive-handling dependencies current and to treat any code path that extracts, unpacks, or writes files based on external input as security-critical by default.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #876

Related Articles

high

clean_path() Path Traversal: rel_path Escapes USERDATA Dir

A path traversal flaw in the `clean_path()` helper let a user-controlled `rel_path` value escape the intended USERDATA directory and reach arbitrary files on disk. The function joined path segments without checking the final result, so `../` sequences in route parameters or query strings could be used to read or write files outside the sandboxed storage area. The fix normalizes the path and verifies it still resolves inside the USERDATA root before returning it, raising an error otherwise.

critical

path.resolve() Path Traversal in Node CLI's Dynamic import()

A Node.js CLI script for validating expression definitions took a file path from `process.argv[2]`, resolved it with `path.resolve()`, and passed the result straight into a dynamic `import()` — with no check that the resolved path stayed inside the working directory. An attacker (or a malicious skill/plugin invocation) could supply traversal sequences to load and execute arbitrary `.js` files from anywhere on disk. The fix adds a boundary check with `path.relative()` and an extension allowlist b

high

bookDir() Path Traversal via Unsanitized bookId Parameter

The `bookDir()` function accepted unsanitized `bookId` values derived from user-created book titles, enabling path traversal attacks through `../` sequences. A fix was applied that validates the identifier using `path.basename()` and throws on mismatch, ensuring all resolved paths remain within `LIBRARY_DIR`.

high

Express `app.get('*')` Wildcard Handler Path Traversal in watch.js

A first-party Express server's wildcard route handler used `req.url.indexOf('font.woff2')` to gate access to a font file, allowing attackers to bypass the substring check with crafted paths. The fix replaces the catch-all handler with explicit route registration.

high

updateCardBg() Follows Unvalidated 302 Location Headers

A background-image updater fetched a configured image URL with manual redirect handling and then re-issued the request to whatever `Location` header came back, with no scheme or host checks. A redirect to `http://169.254.169.254/` or `http://127.0.0.1:<port>/` would have been followed with the original fetch options attached, and the response body written to disk as an image asset. The fix resolves the redirect target against `imgDownloadUrl` and rejects anything that is not HTTPS on the same ho

high

markitdown_bridge.py Path Traversal: Arbitrary File Read via sys.argv

The markitdown_bridge.py script, used by MDView for DOCX-to-Markdown conversion, accepted file paths directly from command-line arguments without validating they stayed within intended directories. An attacker could exploit this to read arbitrary files from the filesystem by passing path traversal sequences in the source_path parameter.