How Denial of Service via Gzip Bomb Happens in Node.js and How to Fix It
Vulnerability at a Glance
| Field | Detail |
|---|---|
| CVE | CVE-2026-59873 |
| Severity | Critical |
| Package | tar (node-tar) |
| Affected version | 7.5.15 (and earlier) |
| Fixed version | 7.5.19+ (upgraded to 7.5.21) |
| CWE | CWE-400: Uncontrolled Resource Consumption |
| Attack type | Denial of Service via crafted gzip bomb |
Introduction
The package-lock.json file in this project locked the tar dependency at version 7.5.15 — a version that contains a critical flaw in its gzip decompression pipeline. When node-tar processes a compressed archive, it streams the decompressed data through a series of internal transforms. In vulnerable versions, there is no hard ceiling on how much data those transforms are allowed to produce. A single HTTP request carrying a few kilobytes of compressed payload can silently instruct the server to allocate gigabytes of memory, bringing the Node.js process — and everything running alongside it — to a halt.
This is the essence of CVE-2026-59873: an attacker does not need credentials, elevated privileges, or insider knowledge. They only need to reach any code path that calls into node-tar with attacker-controlled input.
The Vulnerability Explained
What Is a Gzip Bomb?
A gzip bomb (also called a "zip of death" or decompression bomb) exploits the asymmetry between compressed and uncompressed data. Legitimate gzip files typically compress at ratios of 2:1 to 10:1. A carefully constructed gzip bomb can achieve ratios of 1,000,000:1 or higher — a 10 KB file that decompresses to 10 GB.
The technique works by nesting layers of highly repetitive data, which gzip's DEFLATE algorithm compresses extremely efficiently. When the decompressor expands each layer, memory consumption grows exponentially.
Why node-tar 7.5.15 Is Vulnerable
The vulnerable version locked in package-lock.json was:
"node_modules/tar": {
"version": "7.5.15",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.15.tgz",
"integrity": "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ=="
}
In this version, node-tar's internal gzip decompression stream does not enforce a maximum decompressed byte budget. The extraction pipeline reads compressed chunks, passes them through Node.js's built-in zlib.createGunzip() transform, and writes the resulting bytes into memory without checking whether the cumulative decompressed size has exceeded any reasonable threshold.
A Concrete Attack Scenario
Consider an application that accepts .tar.gz uploads — perhaps a build artifact uploader, a plugin installer, or a data import endpoint. An attacker crafts a gzip bomb:
- They create a file containing millions of repeated null bytes.
- They compress it with gzip, producing a ~50 KB file.
- They POST this file to the upload endpoint.
- The server calls
tar.extract()on the uploaded stream. - node-tar 7.5.15 begins decompressing: 50 KB → 500 MB → 5 GB → process OOM crash.
No authentication is required. No special privileges are needed. The attack is repeatable and can be automated. Even if the OS kills the Node.js process, the attacker can immediately repeat the request, effectively keeping the service offline indefinitely.
Real-World Impact for This Application
The PR notes that the vulnerable path is present in the dependency tree but not confirmed reachable — meaning the tar package is pulled in as a transitive dependency (likely through build tooling or a plugin system). Even transitive exposure matters: if any code path in the dependency tree calls tar.extract() or tar.parse() on user-supplied data, the application is exploitable. Given the critical severity rating, treating this as exploitable is the correct posture.
The Fix
What Changed
The fix makes two coordinated changes: upgrading the resolved version in package-lock.json and pinning the version in package.json via an overrides block.
package-lock.json — Version Upgrade
Before:
"node_modules/tar": {
"version": "7.5.15",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.15.tgz",
"integrity": "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ=="
}
After:
"node_modules/tar": {
"version": "7.5.21",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.21.tgz",
"integrity": "sha512-XdhtCvlMywwxpCW8YEq3lOXBJpUPTR2OHHcwLPO3HwsJqOHa2Ok/oJ7ruGzp+JrKoRPVCzJwAdEjqLW/vNRPHA=="
}
The new integrity hash (sha512-XdhtCvl...) cryptographically guarantees that npm installs exactly the patched binary — not a cached copy of the vulnerable version.
package.json — Overrides Block
Before:
{
"devDependencies": {
"@tauri-apps/cli": "^2.11.4"
}
}
After:
{
"devDependencies": {
"@tauri-apps/cli": "^2.11.4"
},
"overrides": {
"tar": "7.5.21"
}
}
This is the more important of the two changes. Without the overrides block, the next npm install or npm update could silently resolve tar back to a vulnerable version if any transitive dependency specifies a loose version range like "tar": "^7.0.0". The overrides block hard-pins tar to 7.5.21 across the entire dependency tree, regardless of what individual packages request.
How the Patch Fixes the Problem
node-tar 7.5.19 introduced decompressed-size accounting in its gzip stream handling. The patched version tracks the total number of bytes produced by the decompressor and aborts extraction if that count exceeds a configurable — but sensibly defaulted — threshold. This means a 50 KB gzip bomb that would have expanded to 5 GB now causes a controlled error rather than a memory exhaustion crash.
The fix is backward-compatible: valid archives that stay within reasonable size bounds are unaffected. The PR description confirms: "it only tightens handling of untrusted input and leaves valid inputs unaffected."
Prevention & Best Practices
1. Always Pin Transitive Dependencies That Handle Archives
Don't rely on semver ranges alone for security-sensitive packages. Use npm overrides (npm 8.3+), yarn resolutions, or pnpm.overrides to enforce minimum safe versions across your entire dependency tree:
// package.json
"overrides": {
"tar": ">=7.5.19"
}
2. Integrate Dependency Scanning Into CI/CD
Tools like Trivy, Snyk, npm audit, and OWASP Dependency-Check can catch vulnerable versions before they reach production. Add a step to your pipeline:
# Example: fail the build if any critical CVEs are found
trivy fs --exit-code 1 --severity CRITICAL .
3. Enforce Decompression Size Limits at the Application Layer
Even with a patched library, apply defense-in-depth. If your application extracts archives, add your own size checks:
const MAX_UNCOMPRESSED_BYTES = 500 * 1024 * 1024; // 500 MB
let totalBytes = 0;
tarStream.on('entry', (entry) => {
entry.on('data', (chunk) => {
totalBytes += chunk.length;
if (totalBytes > MAX_UNCOMPRESSED_BYTES) {
tarStream.destroy(new Error('Archive exceeds size limit'));
}
});
});
4. Never Extract Untrusted Archives With Default Settings
If your application accepts archive uploads from users, treat every archive as potentially malicious. Run extraction in a sandboxed worker process with memory limits:
# Node.js worker with a 256 MB heap limit
node --max-old-space-size=256 extract-worker.js
5. Monitor for Dependency Drift
Schedule regular npm audit runs and automated dependency update PRs (e.g., via Dependabot or Renovate) to catch newly disclosed CVEs quickly.
Relevant Standards
- CWE-400: Uncontrolled Resource Consumption — https://cwe.mitre.org/data/definitions/400.html
- OWASP A05:2021 – Security Misconfiguration covers failure to update and harden components
- OWASP Dependency Check is the canonical tool reference for this class of issue
Key Takeaways
tar7.5.15 inpackage-lock.jsonwas the exact vulnerable artifact — even a transitive lock on this version is enough to expose your application to CVE-2026-59873.- Upgrading
package-lock.jsonalone is insufficient — without theoverridesblock inpackage.json, the nextnpm installcan silently reintroduce the vulnerable version through a transitive dependency's loose version range. - Gzip bombs are cheap to craft and devastating to unpatched servers — a ~50 KB file can consume gigabytes of memory, making this a high-leverage attack for any service that processes user-supplied archives.
- The
overridespattern is the correct npm mechanism for enforcing a minimum safe version of a transitive dependency across an entire project, and should be part of every security fix for indirect dependencies. - Static analysis tools like Trivy can catch this class of vulnerability before it reaches production by scanning
package-lock.jsonagainst the CVE database — integrate them into your CI pipeline now.
How Orbis AppSec Detected This
- Source: The tainted data enters wherever user-supplied or network-fetched compressed archive data is passed to node-tar's extraction API (e.g.,
tar.extract()ortar.parse()). - Sink: node-tar's internal gzip decompression stream in
node_modules/tarversion7.5.15, which performs unbounded decompression without a byte-count ceiling. - Missing control: No maximum decompressed-size limit was enforced in the tar extraction pipeline, allowing a crafted gzip bomb to exhaust process memory.
- CWE: CWE-400 — Uncontrolled Resource Consumption.
- Fix: The
tardependency was upgraded from7.5.15to7.5.21inpackage-lock.json, and a"overrides": { "tar": "7.5.21" }block was added topackage.jsonto prevent transitive dependency resolution from reverting to a vulnerable 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-59873 is a sharp reminder that Denial of Service vulnerabilities in archive-handling libraries are not theoretical. A single crafted gzip bomb — trivial to produce — can bring down a Node.js service running an unpatched version of tar. The fix here is precise and minimal: upgrade to 7.5.21 and pin the version with an overrides block to prevent regression. More broadly, any application that touches user-supplied compressed data should treat decompression as a resource-consumption risk and apply both library-level patches and application-level size guards. Keep your dependency tree shallow, your locks tight, and your scanners running on every commit.