How Denial of Service via Gzip Bomb Happens in Node.js and How to Fix It
The Threat Hidden in Your package-lock.json
Every Node.js project that processes compressed archives — uploads, build artifacts, package installs — is only as safe as the libraries doing the decompression. When Trivy scanned this project's package-lock.json, it found tar pinned at version 7.5.11: a version vulnerable to CVE-2026-59873, a critical Denial of Service flaw triggered by a crafted gzip bomb.
The fix — upgrading to 7.5.21 and locking the version via package.json overrides — is small in diff size but significant in impact. This post unpacks exactly what the vulnerability is, how an attacker would exploit it, and what every Node.js developer should take away.
The Vulnerability Explained
What Is a Gzip Bomb?
A gzip bomb is a compressed archive engineered to have an extreme compression ratio. The canonical example is a file a few kilobytes in size that decompresses into gigabytes of data. The compression format itself is legitimate — the bomb exploits the absence of decompression size limits in the consuming library.
When node-tar 7.5.11 receives a .tar.gz stream, it pipes the gzip-compressed bytes through a decompressor and then processes the resulting tar entries. In the vulnerable version, there is no enforced ceiling on how many decompressed bytes the gzip layer is allowed to produce. An attacker who can supply a crafted archive to a code path using node-tar can therefore cause:
- Memory exhaustion: the decompressed buffer grows until the Node.js heap is full and the process crashes or is OOM-killed.
- CPU starvation: the event loop is blocked processing an ever-expanding decompression stream, making the server unresponsive to legitimate requests.
The Vulnerable Package Entry
Before the fix, package-lock.json contained:
"node_modules/tar": {
"version": "7.5.11",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.11.tgz",
"integrity": "sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ=="
}
And in the legacy dependencies section:
"tar": {
"version": "7.5.11",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.11.tgz",
"integrity": "sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ=="
}
Both entries point to the same vulnerable tarball. Any code in the dependency tree that calls tar.extract(), tar.x(), or any streaming extraction API from this package inherits the flaw.
A Concrete Attack Scenario
Consider a common pattern in Node.js build tools or file-processing services:
const tar = require('tar');
// User-supplied archive path or piped upload stream
await tar.x({
file: userSuppliedArchivePath,
cwd: '/tmp/workspace'
});
An attacker crafts a .tar.gz file where the gzip layer compresses, say, 500 MB of null bytes down to ~50 KB. They upload this file or supply it as a build artifact. When the server calls tar.x(), node-tar 7.5.11 begins decompressing without limit. Within seconds, the Node.js process has consumed all available memory, the heap crashes, and the service goes down — a classic Denial of Service with no authentication required beyond the ability to supply a file.
Because the vulnerability is in the decompression layer (before tar entries are even parsed), no amount of post-extraction validation helps. The damage is done during the read.
The Fix
Upgrading the Dependency
The fix involves three coordinated changes across two files.
Change 1 — node_modules/tar in package-lock.json:
- "version": "7.5.11",
- "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.11.tgz",
- "integrity": "sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ==",
+ "version": "7.5.21",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.21.tgz",
+ "integrity": "sha512-XdhtCvlMywwxpCW8YEq3lOXBJpUPTR2OHHcwLPO3HwsJqOHa2Ok/oJ7ruGzp+JrKoRPVCzJwAdEjqLW/vNRPHA==",
Change 2 — legacy dependencies.tar entry in package-lock.json:
- "version": "7.5.11",
+ "version": "7.5.21",
Both the node_modules/tar block (used by npm v7+ for the flat install) and the legacy nested dependencies block are updated. Updating only one would leave the other as a potential resolution source depending on the npm version and install flags used in the CI pipeline.
Change 3 — package.json overrides:
+ "overrides": {
+ "tar": "7.5.21"
+ }
This is the most important defensive layer. npm's overrides field forces every package in the dependency tree — direct or transitive — to resolve tar to exactly 7.5.21. Without this, a future npm install or a transitive dependency update could silently pull tar@^7.5.4 (the range that was previously specified) back to a vulnerable patch version.
Note also the pinning change in the transitive dependency block:
- "tar": "^7.5.4",
+ "tar": "7.5.21",
The caret (^) range was replaced with an exact version, eliminating the window where a semver-compatible but vulnerable release could be resolved.
Why 7.5.21 Instead of 7.5.19?
The PR title references 7.5.19 as the first fixed version (per the CVE advisory), but the actual resolved version in the diff is 7.5.21. This is correct practice: when a patch series is available, pinning to the latest patch in the series picks up any additional correctness or security fixes that landed after the initial CVE patch, without introducing breaking changes (patch versions are semver-compatible).
Prevention & Best Practices
1. Always Set overrides for Security-Critical Dependencies
The overrides field in package.json is your last line of defense against transitive dependency vulnerabilities. When a CVE is fixed in a library, add an override immediately so that no part of your dependency tree can resolve the vulnerable version:
"overrides": {
"tar": "7.5.21"
}
For Yarn users, the equivalent is resolutions.
2. Run Dependency Audits in CI
Integrate npm audit or a dedicated scanner (Trivy, Snyk, Dependabot) into your CI pipeline. This vulnerability was caught by Trivy scanning package-lock.json — a step that costs nothing to add to a GitHub Actions workflow:
- name: Security audit
run: npx trivy fs --exit-code 1 --severity CRITICAL,HIGH .
3. Validate and Limit Archive Processing
Where your application logic allows it, add upstream controls before calling tar.x():
- File size limit: reject archives above a reasonable threshold before extraction begins.
- Content-Type validation: verify the MIME type server-side, not just client-supplied headers.
- Sandboxed extraction: run extraction in a subprocess with memory limits (
--max-old-space-size) or in a container with cgroup memory constraints.
These controls don't replace the library fix but provide defense-in-depth.
4. Pin Exact Versions for Security-Sensitive Packages
Semver ranges like ^7.5.4 are convenient but dangerous for security-sensitive packages. Consider pinning exact versions for libraries that handle untrusted data (archive extraction, image processing, XML parsing) and automating version bumps via Dependabot or Renovate with mandatory review.
5. Understand CWE-400
This vulnerability is classified under CWE-400: Uncontrolled Resource Consumption. The OWASP guidance on this class of vulnerability recommends:
- Imposing limits on all resource-consuming operations that process external input.
- Treating compressed data as untrusted even when the compression format itself is standard.
- Monitoring resource usage in production to detect anomalous consumption early.
Key Takeaways
tar@7.5.11inpackage-lock.jsonis exploitable: any code path callingtar.x()ortar.extract()with attacker-controlled input could crash the Node.js process via a crafted gzip bomb.- Updating
package-lock.jsonalone is insufficient: without theoverridesentry inpackage.json, a futurenpm installcan re-introduce a vulnerable version through transitive dependencies that specifytar: "^7.5.4". - The
^semver operator is a liability for security patches: replacing"tar": "^7.5.4"with"tar": "7.5.21"closes the resolution window that allowed the vulnerable version to persist. - Decompression attacks bypass post-extraction controls: validating extracted file contents does not help — the DoS occurs during the gzip decompression phase, before any tar entry is parsed.
- Trivy's static analysis of
package-lock.jsonis effective: this vulnerability was detected without running the application, demonstrating the value of scanning lockfiles as part of every CI build.
How Orbis AppSec Detected This
- Source: Untrusted compressed archive data entering any code path that calls
tar.x(),tar.extract(), or the streamingtar.Parse()API — potentially from HTTP file uploads, build artifact downloads, or piped stdin. - Sink: The gzip decompression layer inside
node_modules/tar(version7.5.11), which processes the compressed stream without enforcing a maximum decompressed-size limit. - Missing control: No decompressed-byte ceiling in the gzip stream handler, allowing unbounded memory and CPU consumption before any application-level validation can run.
- CWE: CWE-400 — Uncontrolled Resource Consumption.
- Fix: Upgraded
tarfrom7.5.11to7.5.21inpackage-lock.jsonand added"overrides": { "tar": "7.5.21" }inpackage.jsonto prevent transitive re-introduction of the 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 reminder that Denial of Service vulnerabilities in compression libraries are not theoretical — they are practical, low-effort attacks that require no authentication and produce immediate, visible impact. The node-tar gzip bomb flaw in version 7.5.11 could be triggered by anyone who can supply a file to your application, making it especially dangerous in any service that processes user uploads, CI artifacts, or third-party packages.
The fix is straightforward: upgrade to 7.5.21, pin the version in package.json overrides, and integrate lockfile scanning into your CI pipeline so future regressions are caught before they reach production. Small changes in dependency management hygiene have an outsized effect on your application's resilience against this class of attack.