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 |
| CWE | CWE-400 — Uncontrolled Resource Consumption |
| Package | adm-zip < 0.6.0 |
| Affected file | source/plugins/deepseek-ivideo/pnpm-lock.yaml |
| Fix | Upgrade adm-zip to 0.6.0 |
Introduction
The source/plugins/deepseek-ivideo plugin is responsible for handling video-related functionality in a DeepSeek-powered application stack. Buried inside its dependency tree was a ticking clock: adm-zip ^0.5.16, a version range that satisfies itself with 0.5.18 — a release carrying CVE-2026-39244, a high-severity Denial of Service flaw.
The problem is not in the plugin's own code. It is in how adm-zip processes ZIP archives. When adm-zip reads a ZIP file, it trusts the metadata fields embedded in the archive's central directory — fields like the uncompressed size of each entry. In versions before 0.6.0, the library allocates a buffer sized to match that field before verifying whether the actual compressed data supports it. An attacker who can feed a crafted ZIP to the application can declare an uncompressed size of, say, 4 GB for a 1 KB payload, causing Node.js to attempt a 4 GB heap allocation — and crash.
For developers building plugins that download, extract, or process ZIP archives (browser bundles, font packages, video assets), this is a very real attack surface.
The Vulnerability Explained
What adm-zip Does and Where It Goes Wrong
adm-zip is a pure-JavaScript library for reading and writing ZIP archives without native bindings. It is popular precisely because it requires no compilation step — ideal for cross-platform plugins like deepseek-ivideo.
When adm-zip opens a ZIP file, it reads the End of Central Directory (EOCD) record and then iterates over each file entry in the Central Directory. Each entry contains two size fields:
compressedSize— how many bytes are stored on diskuncompressedSize— how many bytes the entry expands to after decompression
In adm-zip < 0.6.0, the library uses uncompressedSize to pre-allocate the output buffer:
// Simplified representation of the vulnerable pattern in adm-zip 0.5.x
var data = Buffer.alloc(entry.header.size); // 'size' comes directly from ZIP metadata
// ... then decompress into 'data'
There is no upper-bound check on entry.header.size before Buffer.alloc() is called. A crafted ZIP can set this field to 0xFFFFFFFF (4,294,967,295 bytes — ~4 GB). Node.js will attempt to honor that allocation. On most production servers, this either:
- Triggers an out-of-memory crash (
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed) - Causes severe memory pressure that degrades all other requests served by the same process
The Vulnerable Dependency Declaration
In package.json, the dependency was declared with a caret range:
// Before — package.json (vulnerable)
"adm-zip": "^0.5.16"
The caret (^) allows npm/pnpm to resolve any 0.5.x version, which means 0.5.18 — the version that carries CVE-2026-39244 — satisfies this range. The lockfile then freezes 0.5.18 as the resolved version:
# Before — pnpm-lock.yaml (vulnerable)
adm-zip:
specifier: ^0.5.16
version: 0.5.18
Attack Scenario Specific to deepseek-ivideo
The deepseek-ivideo plugin uses @puppeteer/browsers and other tooling that may download browser binaries or assets packaged as ZIP archives. If any code path passes a remotely fetched or user-supplied ZIP to adm-zip for extraction, an attacker controlling that ZIP source can:
- Craft a ZIP where one or more entries declare an
uncompressedSizeof several gigabytes. - Deliver this file to the plugin's extraction routine.
- Trigger a single
Buffer.alloc()call that exhausts the Node.js heap. - Crash the entire plugin process — and potentially the host application alongside it.
Because this is a pre-decompression allocation (the crash happens before any bytes are actually decompressed), even stream-based or chunked reading does not help. The damage is done the moment the size field is read.
The Fix
What Changed and Why Each Change Was Necessary
The fix touches three files. Here is what each change does and why it matters.
1. package.json — Pin the version exactly
// Before
"adm-zip": "^0.5.16"
// After
"adm-zip": "0.6.0"
The caret range ^0.5.16 was replaced with an exact pin 0.6.0. This is intentional and important: it prevents any future pnpm install or CI run from accidentally resolving a different (potentially still-vulnerable) patch release. adm-zip 0.6.0 introduced validation that checks uncompressedSize against the actual compressed data size and enforces reasonable limits before any allocation occurs.
2. pnpm-lock.yaml — Add a global override
# After — pnpm-lock.yaml
overrides:
adm-zip: 0.6.0
This is the most security-critical change. The overrides block in pnpm forces all resolutions of adm-zip — whether direct or transitive — to 0.6.0. Without this, a transitive dependency (e.g., some tool in the dev dependency tree) could still pull in 0.5.18 and re-introduce the vulnerability into the installed node_modules. The override acts as a blanket guarantee across the entire dependency graph.
The lockfile entry for adm-zip also changes from:
# Before
adm-zip:
specifier: ^0.5.16
version: 0.5.18
to the resolved 0.6.0 entry, confirming that pnpm will install only the patched version.
3. pnpm-workspace.yaml — Workspace-level consistency
The workspace configuration is updated to ensure the override propagates correctly in a monorepo context, where multiple packages might otherwise resolve their own independent copies of adm-zip.
Why adm-zip 0.6.0 Is Safe
adm-zip 0.6.0 added defensive checks before buffer allocation. The fix validates that the declared uncompressedSize is consistent with the compressedSize and rejects entries where the ratio or absolute size exceeds safe thresholds. This means a crafted ZIP with a 4 GB declared size but 1 KB of actual data will be rejected at parse time rather than triggering a catastrophic allocation.
Prevention & Best Practices
1. Avoid Caret Ranges for Security-Sensitive Libraries
The caret (^) in "adm-zip": "^0.5.16" is convenient for getting patch updates automatically, but it also means your lockfile can drift to a vulnerable minor version if you regenerate it. For libraries that handle untrusted binary formats (ZIP, tar, PDF, image files), consider exact pins or at minimum tilde ranges (~0.5.16, which only allows patch updates).
2. Use pnpm overrides (or npm overrides / yarn resolutions) Proactively
Transitive dependency vulnerabilities are the hardest to catch manually. Adding an overrides block to your lockfile configuration ensures that even if a dependency you don't control pulls in a vulnerable version, your project's install will enforce the safe version.
# pnpm-lock.yaml
overrides:
adm-zip: ">=0.6.0"
3. Run SCA Scans in CI
Trivy detected this vulnerability by matching the installed adm-zip@0.5.18 against its CVE database. Integrate a Software Composition Analysis (SCA) scanner into your CI pipeline:
# Example: Trivy filesystem scan
trivy fs --scanners vuln ./source/plugins/deepseek-ivideo
This catches known-vulnerable dependency versions before they reach production.
4. Validate ZIP Files Before Parsing
Even with a patched library, apply defense-in-depth for any code that processes ZIP files from untrusted sources:
import AdmZip from 'adm-zip';
import fs from 'fs';
const MAX_ZIP_SIZE_BYTES = 50 * 1024 * 1024; // 50 MB
const MAX_ENTRIES = 1000;
function safeExtract(zipPath, outputDir) {
const stats = fs.statSync(zipPath);
if (stats.size > MAX_ZIP_SIZE_BYTES) {
throw new Error(`ZIP file exceeds maximum allowed size: ${stats.size}`);
}
const zip = new AdmZip(zipPath);
const entries = zip.getEntries();
if (entries.length > MAX_ENTRIES) {
throw new Error(`ZIP contains too many entries: ${entries.length}`);
}
zip.extractAllTo(outputDir, true);
}
5. Reference Standards
- CWE-400: Uncontrolled Resource Consumption — the root cause classification for this vulnerability.
- OWASP A05:2021 — Security Misconfiguration: Outdated or misconfigured dependencies fall under this category.
- OWASP Dependency-Check: An open-source SCA tool that integrates with Node.js projects.
Key Takeaways
- The
^version range inpackage.jsonwas the enabler:"adm-zip": "^0.5.16"allowed pnpm to resolve0.5.18, the vulnerable release. Exact pins prevent this class of drift. - A pnpm
overridesblock is essential in monorepos: Withoutoverrides: adm-zip: 0.6.0inpnpm-lock.yaml, transitive dependencies in the deepseek-ivideo workspace could still resolve the vulnerable version independently. - ZIP header metadata is attacker-controlled data: The
uncompressedSizefield in a ZIP's central directory is entirely user-supplied. Any library that allocates memory based on this field without validation is vulnerable to this exact attack pattern. - A single malformed file can crash the entire Node.js process: Unlike SQL injection or XSS, this DoS requires no authentication — just the ability to supply a ZIP file to the application.
- SCA scanning (Trivy) caught what code review would miss: The vulnerability is not in the plugin's own source code; it is in a pinned transitive dependency version. Static code analysis alone would not have found it.
How Orbis AppSec Detected This
- Source: A ZIP archive processed by the
deepseek-ivideoplugin — potentially fetched from a remote URL or supplied by a user as a file upload. - Sink: adm-zip's internal
Buffer.alloc(entry.header.size)call site, whereentry.header.sizeis read directly from the ZIP's central directory metadata without bounds validation. - Missing control: No upper-bound check on
uncompressedSizebefore heap allocation; no rejection of entries where declared size is disproportionate to compressed size. - CWE: CWE-400 — Uncontrolled Resource Consumption.
- Fix: The dependency specifier in
package.jsonwas changed from"^0.5.16"to"0.6.0", and a pnpmoverridesentry was added topnpm-lock.yamlto enforceadm-zip@0.6.0across the entire dependency graph.
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 sharp reminder that the most dangerous vulnerabilities in modern applications often live not in your own code, but in the libraries your code depends on — and the libraries those libraries depend on. A single caret in a package.json file was all it took to expose the deepseek-ivideo plugin to a heap-exhaustion attack requiring nothing more than a crafted ZIP file.
The fix is surgical: pin adm-zip to 0.6.0, add a lockfile override to close the transitive dependency gap, and let the patched library's internal validation do the rest. More broadly, treat any library that parses binary formats from untrusted sources as a high-value security target — keep it pinned, keep it patched, and scan it automatically.