How Denial of Service via ZIP Parsing Happens in Node.js and How to Fix It
The Vulnerability at a Glance
| Field | Detail |
|---|---|
| CVE | CVE-2026-39244 |
| Severity | High |
| Package | adm-zip (npm) |
| Affected versions | < 0.6.0 |
| Fixed version | 0.6.0 |
| CWE | CWE-400: Uncontrolled Resource Consumption |
| Application | Haven (self-hosted chat) |
Summary
CVE-2026-39244 is a high-severity Denial of Service vulnerability in the adm-zip Node.js package (versions prior to 0.6.0) where a specially crafted ZIP file can trigger excessive memory allocation, potentially crashing the host process. The vulnerability was present in the Haven self-hosted chat application, which used adm-zip ^0.5.16 as a direct dependency. The fix upgrades the dependency to ^0.6.0, which includes hardened ZIP entry parsing that prevents unbounded memory allocation from malicious archives.
Introduction
The package-lock.json file in Haven — a self-hosted private chat platform — locked adm-zip at version 0.5.16. This library handles ZIP file creation and extraction throughout the application. Because Haven is a chat platform, it's reasonable to expect that users can upload or share files, making ZIP parsing a user-facing attack surface. A single maliciously constructed .zip file uploaded by an unauthenticated or low-privileged user could have been enough to bring the entire Haven server process down.
The vulnerability isn't in Haven's own code — it lives inside adm-zip's ZIP central directory parser, which trusted attacker-controlled size fields without enforcing any upper bound before allocating memory. This is a textbook case of why dependency version hygiene is a first-class security concern, not just a maintenance chore.
The Vulnerability Explained
How ZIP Files Are Structured (and Why That Matters)
A ZIP archive is not just a bag of compressed files. It contains a central directory at the end of the file — a table of contents that lists every entry along with metadata: file name length, extra field length, comment length, and crucially, the uncompressed size of each entry.
When a ZIP library opens an archive, it reads these metadata fields first and uses them to allocate buffers before decompressing anything. This is a performance optimization: pre-allocate the right amount of memory, then decompress into it.
The problem? Those size fields are entirely attacker-controlled. Nothing in the ZIP specification prevents a malicious actor from writing 0xFFFFFFFF (4,294,967,295 bytes — about 4 GB) as the uncompressed size of a 10-byte file.
What adm-zip 0.5.16 Did Wrong
In adm-zip version 0.5.16, the library read these metadata fields from the ZIP central directory and used them to drive memory allocation without enforcing a reasonable upper bound. A crafted ZIP file with absurdly large size values in its central directory entries would cause the Node.js process to attempt allocating gigabytes of memory — either triggering an out-of-memory crash or consuming enough resources to make the application unresponsive.
The vulnerable dependency declaration in package.json:
// BEFORE — package.json (vulnerable)
"dependencies": {
"adm-zip": "^0.5.16",
...
}
And in package-lock.json, the resolved version:
// BEFORE — package-lock.json (vulnerable)
"node_modules/adm-zip": {
"version": "0.5.16",
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz",
"integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==",
"engines": {
"node": ">=12.0"
}
}
A Concrete Attack Scenario
Imagine a Haven user uploads a file called documents.zip through the chat interface. The file is only 200 bytes on disk, but its central directory contains a single entry with an uncompressed size field set to 0xFFFFFFFF (4 GB).
When Haven's server-side code calls something like:
const AdmZip = require('adm-zip');
const zip = new AdmZip(uploadedFilePath); // triggers central directory parsing
const entries = zip.getEntries(); // iterates entries, may allocate per-entry buffers
The adm-zip 0.5.16 parser reads the 4 GB size field and attempts to allocate a 4 GB buffer. On most Node.js deployments, this either:
- Crashes the process with a fatal
JavaScript heap out of memoryerror, taking down the entire Haven instance for all users. - Triggers aggressive garbage collection and CPU thrashing, causing severe latency for all concurrent users.
Because this is a Node.js single-threaded event loop, one malicious upload affects every connected user simultaneously — making this a highly effective single-shot DoS attack.
The Fix
What Changed
The fix is a two-file change: package.json and package-lock.json. Together they upgrade adm-zip from 0.5.16 to 0.6.0.
package.json — before:
"adm-zip": "^0.5.16",
package.json — after:
"adm-zip": "^0.6.0",
package-lock.json — before:
"node_modules/adm-zip": {
"version": "0.5.16",
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz",
"integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==",
"engines": {
"node": ">=12.0"
}
}
package-lock.json — after:
"node_modules/adm-zip": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.6.0.tgz",
"integrity": "sha512-XleryMhbuksdKtofnWZ9Sk+4CUTbms4Mb/EU32SZwToAyZ5RgVos/ki8n+yr0LWHOGKuakbXTuuYNHLQjhddgg==",
"license": "MIT",
"engines": {
"node": ">=14.0"
}
}
Why This Fix Works
adm-zip 0.6.0 introduces validation of ZIP entry metadata fields before using them to drive memory allocation. The library now checks that declared sizes are consistent with the actual archive size and enforces reasonable limits, preventing a 200-byte ZIP file from claiming it contains 4 GB of data.
The engines field change from >=12.0 to >=14.0 is also notable — Node.js 14 introduced improved Buffer allocation APIs and better out-of-memory handling, which the new version of adm-zip leverages for safer buffer management.
Why Both Files Need to Change
package.jsondefines the acceptable version range (^0.6.0). Without this change, runningnpm installin a fresh environment would still resolve to0.5.x.package-lock.jsonpins the exact resolved version and its integrity hash. This is what actually guarantees reproducible, secure installs in CI/CD pipelines and production deployments. The newintegrityhash (sha512-XleryMhbuksdKtofnWZ9Sk...) cryptographically ensures that only the legitimate 0.6.0 package is installed — not a tampered substitute.
Prevention & Best Practices
1. Treat Archive Metadata as Untrusted Input
Any field in a ZIP, TAR, or other archive format that influences memory allocation is a potential DoS vector. When evaluating ZIP libraries, check whether they validate:
- Uncompressed entry size vs. actual archive size
- Number of entries vs. central directory size
- File name length vs. remaining header bytes
2. Set File Size Limits Before Parsing
Even with a patched library, add application-level guards:
const MAX_ZIP_SIZE_BYTES = 50 * 1024 * 1024; // 50 MB
if (uploadedFile.size > MAX_ZIP_SIZE_BYTES) {
throw new Error('Archive exceeds maximum allowed size');
}
const zip = new AdmZip(uploadedFilePath);
This won't stop all attacks, but it raises the bar significantly.
3. Run Dependency Scanners in CI
The Trivy scanner caught this vulnerability by matching the adm-zip version in package-lock.json against its CVE database. Add dependency scanning to your CI pipeline:
# Example: GitHub Actions with Trivy
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
severity: 'HIGH,CRITICAL'
4. Use Lock Files and Verify Integrity
The package-lock.json integrity hash (sha512-...) is your last line of defense against supply chain attacks. Always commit your lock file and use npm ci (not npm install) in production and CI environments — it verifies integrity hashes and refuses to install if they don't match.
# Use this in CI/CD — it respects the lock file exactly
npm ci
# NOT this — it may update versions
npm install
5. Monitor for New CVEs in Your Dependencies
Subscribe to security advisories for your direct dependencies:
Relevant Standards
- CWE-400: Uncontrolled Resource Consumption — the root cause category for this vulnerability.
- OWASP A06:2021 – Vulnerable and Outdated Components — using
adm-zip 0.5.16after CVE-2026-39244 was published falls squarely into this category.
Key Takeaways
- ZIP metadata is attacker-controlled data: The uncompressed size fields in a ZIP central directory are written by whoever created the archive.
adm-zip0.5.16 used these values to allocate memory without validation — a design assumption that breaks entirely when handling untrusted uploads. - A single malicious upload could crash Haven for all users: Because Node.js runs on a single-threaded event loop, one OOM-triggering ZIP extraction blocks or kills the process for every connected user simultaneously.
- Both
package.jsonandpackage-lock.jsonmust be updated together: Changing onlypackage.jsonleaves the old pinned version in the lock file; changing onlypackage-lock.jsonmeans the nextnpm installcan revert to the vulnerable range. - The
enginesfield bump (>=14.0) signals a meaningful internal change:adm-zip0.6.0's Node.js 14 minimum requirement reflects that it uses newer, safer buffer allocation APIs — not just a cosmetic version bump. - Trivy caught this from
package-lock.jsonalone: You don't need runtime instrumentation to detect this class of vulnerability. Static dependency scanning on your lock file is sufficient — and fast enough to run on every pull request.
How Orbis AppSec Detected This
- Source: A user-supplied ZIP file processed by the Haven application, passed to
adm-zip's archive parser. - Sink:
adm-zip's internal ZIP central directory reader innode_modules/adm-zip(version0.5.16), which allocated buffers sized by attacker-controlled metadata fields. - Missing control: No upper-bound validation on ZIP entry size fields before memory allocation; no application-level archive size cap before invoking the parser.
- CWE: CWE-400 — Uncontrolled Resource Consumption.
- Fix: Upgraded
adm-zipfrom0.5.16to0.6.0in bothpackage.jsonandpackage-lock.json, replacing the vulnerable parser with one that validates ZIP entry metadata before allocating buffers.
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-39244 is a reminder that the attack surface of your application includes every line of code in your node_modules directory, not just the code you write yourself. A single outdated dependency — adm-zip 0.5.16 — was enough to expose Haven's entire user base to a trivial single-request Denial of Service attack.
The fix is surgical and low-risk: two files changed, one version number bumped, and the vulnerability is closed. The harder lesson is systemic: dependency scanning needs to be a continuous, automated process, not a quarterly audit. Every ZIP, TAR, or archive your application processes is a potential resource exhaustion attack waiting to happen if the parsing library doesn't validate what it reads.
Keep your lock files committed, run npm ci in production, and let automated scanners watch your dependency tree so you can focus on building features rather than chasing CVEs.