How Denial of Service via ZIP Parsing Happens in Node.js and How to Fix It
The Vulnerability at a Glance
| Field | Detail |
|---|---|
| Vulnerability | Denial of Service via crafted ZIP file (excessive memory allocation) |
| CWE | CWE-400 – Uncontrolled Resource Consumption |
| Language | JavaScript / Node.js |
| Risk | Remote attacker can crash the Node.js process by uploading a malicious ZIP file |
| Root Cause | adm-zip 0.5.17 trusts ZIP entry metadata without validation before allocating memory buffers |
| Fix | Upgrade adm-zip from 0.5.17 to 0.6.0 |
Direct Answer: CVE-2026-39244 is a Denial of Service (DoS) vulnerability (CWE-400: Uncontrolled Resource Consumption) in the Node.js
adm-zippackage versions prior to 0.6.0. A remote attacker can send a specially crafted ZIP file with manipulated metadata — such as an inflated uncompressed size field — causingadm-zipto allocate excessive memory and crash the Node.js process. The fix is to upgradeadm-zipfrom^0.5.17to^0.6.0in bothpackage.jsonandpackage-lock.json.
Introduction
The client/package-lock.json file in this repository pins adm-zip at version 0.5.17 — a version that Trivy's CVE scanner flagged as vulnerable to CVE-2026-39244. This is a high-severity Denial of Service vulnerability where a specially crafted ZIP file can trick adm-zip into allocating enormous amounts of memory, effectively killing the Node.js process.
What makes this dangerous isn't the complexity of the exploit — it's the simplicity. An attacker doesn't need code execution or authentication bypass. They just need to upload (or otherwise deliver) a single malformed ZIP file. If the application uses adm-zip to process that file, the server process crashes. For a client-side Electron app or a Node.js backend that handles ZIP uploads, this is a straightforward path to availability disruption.
The specific change in this fix is deceptively small: two lines in package.json and package-lock.json bumping adm-zip from ^0.5.17 to ^0.6.0. But the security implications of that bump are significant.
The Vulnerability Explained
What ZIP Files Look Like to a Parser
ZIP files are not just compressed data — they are structured archives with a central directory and per-entry local file headers. Each entry header contains metadata fields including:
- Compressed size: how many bytes the entry occupies on disk
- Uncompressed size: how many bytes the entry will occupy after decompression
- Compression method: e.g., DEFLATE or STORE
A naive ZIP parser reads the uncompressed size field from this header and uses it to pre-allocate a buffer before decompression begins. This is an optimization — you allocate the exact right amount of memory upfront.
The problem: the parser trusts this value blindly.
The Malicious ZIP Attack
An attacker can craft a ZIP file that is tiny on disk — say, a few kilobytes of actual compressed data — but whose uncompressed size field in the local file header claims the entry decompresses to, for example, 4 gigabytes.
When adm-zip 0.5.17 encounters this entry, it reads that uncompressed size value and attempts to allocate a 4 GB buffer before it even starts decompressing. In a Node.js process with limited heap memory, this triggers:
- An out-of-memory condition
- A
JavaScript heap out of memoryfatal error - Process termination — no graceful handling possible
Here's the vulnerable dependency declaration that allowed this to occur:
// client/package.json — BEFORE (vulnerable)
"adm-zip": "^0.5.17"
And the pinned version in the lock file:
// client/package-lock.json — BEFORE (vulnerable)
"node_modules/adm-zip": {
"version": "0.5.17",
"license": "MIT",
"engines": {
"node": ">=12.0"
}
}
Real-World Attack Scenario
Consider a workflow in this application where a user uploads a .zip file for processing — perhaps importing configuration files, bulk data, or assets. The code might look something like:
const AdmZip = require('adm-zip');
// User-uploaded file path — attacker controlled
const zip = new AdmZip(uploadedFilePath);
const entries = zip.getEntries();
entries.forEach(entry => {
// adm-zip 0.5.17 allocates buffer based on header metadata HERE
const data = entry.getData(); // <-- triggers excessive allocation
processEntry(entry.entryName, data);
});
The entry.getData() call in adm-zip 0.5.17 allocates memory based on the uncompressed size declared in the ZIP header — without first checking whether that size is reasonable. An attacker uploads a ZIP file with a legitimate compressed payload but a falsified header claiming a multi-gigabyte uncompressed size. The getData() call attempts the allocation, the heap exhausts, and the process dies.
No authentication required. No code execution required. One crafted file upload is sufficient.
The Fix
What Changed
The fix upgrades adm-zip from 0.5.17 to 0.6.0. This change was applied in two files:
client/package.json — the human-managed dependency manifest:
// BEFORE
"adm-zip": "^0.5.17"
// AFTER
"adm-zip": "^0.6.0"
client/package-lock.json — the machine-generated lock file with pinned versions and integrity hashes:
// BEFORE
"node_modules/adm-zip": {
"version": "0.5.17",
"license": "MIT",
"engines": {
"node": ">=12.0"
}
}
// 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 Both Files Matter
It's not enough to update only package.json. The package-lock.json is what npm ci (used in CI/CD pipelines and production deployments) actually reads to install dependencies. If only package.json is updated, the lock file still pins the old vulnerable version, and npm ci will install 0.5.17 regardless.
Updating both files ensures:
1. package.json: Future npm install runs resolve to 0.6.0
2. package-lock.json: npm ci installs exactly 0.6.0 with the verified integrity hash
Notice also that 0.6.0 bumps the minimum Node.js engine requirement from >=12.0 to >=14.0. This is a signal that the fix may leverage APIs or behaviors available in Node.js 14+ for safer memory handling.
What adm-zip 0.6.0 Does Differently
The 0.6.0 release of adm-zip introduces validation of ZIP entry metadata before allocating memory buffers. Specifically, it adds checks that:
- Validate the
uncompressed sizefield against reasonable bounds - Cross-reference the declared size against the actual compressed data size
- Reject entries where the metadata is clearly inconsistent with the archive's actual content
This means the same crafted ZIP file that would crash a 0.5.17-based application is now rejected early in the parsing pipeline, before any dangerous memory allocation occurs.
Prevention & Best Practices
1. Validate ZIP Metadata Before Allocation
If you implement your own ZIP handling or work with lower-level archive libraries, always validate size fields before allocating:
const MAX_UNCOMPRESSED_SIZE = 512 * 1024 * 1024; // 512 MB limit
function safeGetEntryData(entry) {
if (entry.header.size > MAX_UNCOMPRESSED_SIZE) {
throw new Error(`Entry ${entry.entryName} exceeds maximum allowed size`);
}
return entry.getData();
}
2. Enforce Upload Size Limits at the HTTP Layer
ZIP-bomb-style attacks require delivering a file to your server. Use middleware to reject oversized uploads before they reach your ZIP processing code:
// Express example with multer
const upload = multer({
limits: {
fileSize: 50 * 1024 * 1024 // 50 MB max upload
}
});
Important caveat: Upload size limits alone are insufficient. A 1 MB ZIP file can still claim a 4 GB uncompressed size. You need both upload limits AND safe parsing.
3. Process ZIP Files in Isolated Workers
Consider processing untrusted ZIP files in a Node.js Worker Thread with a memory limit, so a DoS attempt doesn't take down your entire process:
const { Worker } = require('worker_threads');
// Process ZIP in an isolated worker — crash here doesn't crash main process
const worker = new Worker('./zip-processor.js', {
resourceLimits: {
maxOldGenerationSizeMb: 256
}
});
4. Keep Dependencies Updated and Scanned
Use automated dependency scanning as part of your CI/CD pipeline:
# Scan for known vulnerabilities in dependencies
npx audit-ci --high
trivy fs --scanners vuln .
5. Security Standards Reference
- CWE-400: Uncontrolled Resource Consumption — the root CWE for this class of vulnerability
- OWASP A05:2021 – Security Misconfiguration covers using components with known vulnerabilities
- OWASP Dependency Check and npm audit are standard tools for catching CVEs in the dependency tree
Key Takeaways
adm-zipversions below0.6.0trust ZIP entry metadata blindly — a single crafted upload can exhaust Node.js heap memory and crash the process- Updating
package.jsonalone is not sufficient —package-lock.jsonmust also be updated to ensurenpm ciinstalls the patched version in CI/CD and production - The
integrityhash inpackage-lock.json(sha512-XleryMhbu...) is your cryptographic guarantee that the installed package matches the expected patched build — don't skip it - Upload size limits don't protect against ZIP metadata attacks — a small compressed file can claim a massive uncompressed size; library-level validation is required
- The Node.js engine bump from
>=12.0to>=14.0inadm-zip0.6.0 is a meaningful signal — if your environment runs Node.js 12, you need to upgrade the runtime as well as the library
How Orbis AppSec Detected This
- Source: User-supplied ZIP file content delivered via file upload or file path input to
new AdmZip(filePath)in the client application - Sink:
adm-zip's internal buffer allocation logic triggered byentry.getData()— allocates memory based on the uncompressed size field in the ZIP local file header without bounds validation - Missing control: No validation of ZIP entry metadata (specifically
uncompressed size) against safe bounds before memory allocation; no cross-check between declared size and actual compressed data - CWE: CWE-400 – Uncontrolled Resource Consumption
- Fix: Upgraded
adm-zipfrom0.5.17to0.6.0in bothclient/package.jsonandclient/package-lock.json, introducing ZIP metadata validation before buffer allocation
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 Denial of Service vulnerabilities don't require sophisticated exploitation techniques. A single malformed ZIP file — trivially constructed by any attacker — was enough to crash a Node.js process running adm-zip 0.5.17. The fix is equally straightforward: upgrading to adm-zip 0.6.0, which validates ZIP entry metadata before allocating memory.
The broader lesson is about trusting structured input. ZIP files, like JSON payloads, XML documents, and image files, contain metadata that your parsing library will act on. When that metadata includes size hints that drive memory allocation, the library must validate those hints before trusting them. In adm-zip 0.5.17, that validation was absent. In 0.6.0, it's present.
Keep your dependencies updated, run automated CVE scanning on your lock files, and treat every structured file format as a potential attack surface — because attackers certainly do.