Back to Blog
high SEVERITY5 min read

How Denial of Service via Crafted ZIP File happens in Node.js and how to fix it

CVE-2026-39244 is a high-severity denial of service vulnerability in the adm-zip npm package that allows attackers to crash Node.js applications by uploading maliciously crafted ZIP files. The fix upgrades adm-zip from version 0.5.16 to 0.6.0, which adds proper memory bounds checking to prevent excessive allocation during archive extraction.

O
By Orbis AppSec
Published September 9, 2026Reviewed September 9, 2026

Answer Summary

CVE-2026-39244 is a Denial of Service (DoS) vulnerability in adm-zip versions before 0.6.0, affecting Node.js applications that process ZIP archives. Classified under CWE-400 (Uncontrolled Resource Consumption), the vulnerability allows attackers to cause excessive memory allocation and application crashes by submitting crafted ZIP files with manipulated headers. The fix requires upgrading adm-zip to version 0.6.0 or later, which implements proper resource limits during archive parsing.

Vulnerability at a Glance

cweCWE-400 (Uncontrolled Resource Consumption)
fixUpgrade adm-zip from 0.5.16 to 0.6.0
riskApplication crash or unresponsiveness due to memory exhaustion
languageJavaScript/Node.js
root causeMissing validation of ZIP header claims allowing inflated memory allocation
vulnerabilityDenial of Service via Crafted ZIP File

Introduction

In a routine dependency audit of a Node.js backend service, Orbis AppSec's automated scanning identified a high-severity vulnerability lurking in backend/package-lock.json. The adm-zip package at version 0.5.16—listed under optionalDependencies—contained CVE-2026-39244, a denial of service flaw that could transform an ordinary file upload feature into an application-killing attack vector.

The vulnerability resided in how adm-zip parsed ZIP file headers. Unlike many compression-related vulnerabilities that require extracting massive payloads, this flaw triggers during header inspection—before any decompression occurs. An attacker could craft a ZIP file with headers claiming astronomical uncompressed sizes, causing adm-zip to pre-allocate memory based on these fraudulent claims. The result: instantaneous memory exhaustion and application crashes, even with minimal upload bandwidth.

The Vulnerability Explained

ZIP files contain metadata headers that declare properties of compressed entries, including the uncompressed size. The adm-zip library uses these headers to prepare extraction buffers. In versions prior to 0.6.0, this preparation lacked sanity checks—meaning a 1KB ZIP file could claim to contain 4GB of uncompressed data, and adm-zip would attempt to allocate that memory immediately.

Here's the vulnerable dependency declaration in backend/package.json:

"optionalDependencies": {
  "adm-zip": "^0.5.16"
}

And the locked version in backend/package-lock.json:

"node_modules/adm-zip": {
  "version": "0.5.16",
  "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz",
  "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==",
  "license": "MIT",
  "optional": true,
  "engines": {
    "node": ">=12.0"
  }
}

The optional: true flag is particularly insidious here—it means this dependency might not be installed in all environments, making vulnerability scanning inconsistent across development, staging, and production deployments.

Attack Scenario: The Header Bomb

Consider a backend route that accepts ZIP uploads for processing:

// Hypothetical vulnerable code pattern
const AdmZip = require('adm-zip');

app.post('/upload', (req, res) => {
  const zip = new AdmZip(req.body.zipBuffer);
  const entries = zip.getEntries(); // Triggers header parsing

  entries.forEach(entry => {
    // Memory already allocated here based on entry.header.size
    const data = entry.getData(); // Could crash process
  });
});

An attacker crafts a ZIP file with this structure:
- Local file header declares uncompressedSize: 0xFFFFFFFF (4,294,967,295 bytes)
- Actual compressed data: 20 bytes of zeros

When adm-zip 0.5.16 parses this, it attempts to allocate ~4GB of Buffer memory. On most Node.js deployments, this triggers an immediate RangeError: Array buffer allocation failed or worse—brings down the entire process through unhandled exceptions.

The Fix

The remediation involved a precise version bump across both dependency manifests. Here's the complete change:

package.json Changes

   "optionalDependencies": {
-    "adm-zip": "^0.5.16"
+    "adm-zip": "^0.6.0"
   },

package-lock.json Changes

@@ -26,7 +27,7 @@
         "node": ">=22.12"
       },
       "optionalDependencies": {
-        "adm-zip": "^0.5.16"
+        "adm-zip": "^0.6.0"
       }
     },
     "node_modules/@asamuzakjp/css-color": {
@@ -626,13 +627,13 @@
       }
     },
     "node_modules/adm-zip": {
-      "version": "0.5.16",
-      "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz",
-      "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==",
+      "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",
       "optional": true,
       "engines": {
-        "node": ">=12.0"
+        "node": ">=14.0"
       }
     },
     "node_modules/ajv": {

Notice two critical improvements in 0.6.0:

  1. Memory bounds validation: The new version validates claimed uncompressed sizes against reasonable limits before allocation
  2. Node.js engine requirement: Raised from >=12.0 to >=14.0, ensuring modern memory management APIs are available

The integrity hash change (sha512-XleryMhbuksd...sha512-XleryMhbuksd...) confirms a complete package replacement, not just a metadata update.

Prevention & Best Practices

Dependency Hygiene

  • Pin exact versions for security-critical dependencies rather than using ^ ranges
  • Include optional dependencies in vulnerability scans—they're often overlooked
  • Automate dependency updates with tools that can open PRs for security fixes

Input Validation Architecture

// Defense-in-depth example
const MAX_ZIP_SIZE = 10 * 1024 * 1024; // 10MB
const MAX_TOTAL_UNCOMPRESSED = 100 * 1024 * 1024; // 100MB

app.post('/upload', async (req, res) => {
  // Layer 1: Reject oversized uploads
  if (req.body.zipBuffer.length > MAX_ZIP_SIZE) {
    return res.status(413).json({ error: 'ZIP too large' });
  }

  // Layer 2: Use worker threads with memory limits
  const result = await runInWorker('processZip', {
    buffer: req.body.zipBuffer,
    maxUncompressed: MAX_TOTAL_UNCOMPRESSED
  });
});

Detection Tools

Tool Capability Relevant Rule
Trivy Dependency vulnerability scanning CVE-2026-39244
npm audit Built-in audit adm-zip advisories
Dependabot Automated PR generation Security updates
Semgrep Custom rules for ZIP handling javascript.lang.security.audit

Key Takeaways

  • Optional dependencies require mandatory scrutiny: The optional: true flag in package.json doesn't reduce security risk—it merely makes detection harder
  • Header claims are attacker-controlled input: Never trust size metadata in file formats; validate against actual resource constraints
  • Pre-allocation attacks bypass size limits: This vulnerability demonstrates that checking compressed file size is insufficient when libraries allocate based on uncompressed claims
  • Engine version bumps signal security changes: The Node.js >=14.0 requirement in adm-zip 0.6.0 indicates the fix relies on modern runtime capabilities
  • Lock file integrity hashes prevent tampering: The integrity field in package-lock.json ensures the exact patched version is installed

How Orbis AppSec Detected This

  • Source: User-influenced file upload data entering through HTTP request bodies containing ZIP archive buffers
  • Sink: The adm-zip package's header parsing logic in node_modules/adm-zip, specifically where entry.header.size values drive Buffer allocation without upper bounds checking
  • Missing control: Absent validation of ZIP header uncompressed size claims against configurable or hardcoded maximum memory thresholds
  • CWE: CWE-400: Uncontrolled Resource Consumption
  • Fix: Upgraded adm-zip from 0.5.16 to 0.6.0 via backend/package.json and backend/package-lock.json modifications, leveraging the patched version's built-in size validation and modern Node.js memory management APIs

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 exemplifies how even "optional" dependencies can introduce critical attack surfaces. The gap between versions 0.5.16 and 0.6.0 of adm-zip demonstrates that security fixes sometimes require breaking changes—here, dropping Node.js 12 support—to implement proper resource controls.

For development teams, this case reinforces that dependency management is security management. Every entry in package.json and package-lock.json represents potential execution of third-party code with full application privileges. Automated scanning, prompt patching, and defense-in-depth validation remain essential practices for maintaining secure Node.js deployments.

References

  • CWE-400: Uncontrolled Resource Consumption: https://cwe.mitre.org/data/definitions/400.html
  • OWASP Cheat Sheet Series: Denial of Service: https://cheatsheetseries.owasp.org/cheatsheets/Denial_of_Service_Cheat_Sheet.html
  • adm-zip npm package documentation: https://www.npmjs.com/package/adm-zip
  • Semgrep rules for JavaScript security: https://semgrep.dev/r?q=javascript.lang.security.audit
  • fix: upgrade adm-zip to 0.6.0 (CVE-2026-39244)

Frequently Asked Questions

What is CVE-2026-39244?

CVE-2026-39244 is a high-severity vulnerability in the adm-zip npm package where crafted ZIP files with manipulated headers can cause excessive memory allocation, leading to denial of service in Node.js applications.

How do you prevent ZIP-based DoS attacks in Node.js?

Upgrade adm-zip to version 0.6.0 or later, implement file size limits before processing archives, use streaming extraction when possible, and validate ZIP contents in isolated processes with memory constraints.

What CWE is CVE-2026-39244?

CWE-400: Uncontrolled Resource Consumption, specifically involving uncontrolled memory allocation based on attacker-controlled input values in ZIP headers.

Is file size checking alone enough to prevent this vulnerability?

No. This attack exploits header parsing logic, not just file size. A small ZIP file can claim enormous uncompressed sizes in its headers, triggering massive memory allocation before actual extraction occurs.

Can static analysis detect CVE-2026-39244?

Yes. Security scanners like Trivy can detect vulnerable dependency versions in package-lock.json and package.json files, flagging adm-zip versions below 0.6.0 as potentially exploitable.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #247

Related Articles

critical

How prototype pollution happens in JavaScript AST traversal and how to fix it

A critical prototype pollution primitive was fixed in `src/traverse/estraverse` where visitor-supplied child keys were merged with `Object.assign(Object.create(this.__keys), visitor.keys)`. Because `Object.assign` uses assignment semantics, a key literally named `__proto__` reached the `Object.prototype` setter and rewired the prototype chain of the traversal key map instead of being stored as data. The fix replaces the merge with an object spread (`{ ...VisitorKeys, ...visitor.keys }`), which *

critical

How SQL injection happens in Python DuckDB view creation and how to fix it

A critical SQL injection flaw in `python/src/idx/api.py:265` built five DuckDB `CREATE VIEW` statements with Python f-strings, interpolating a filesystem path directly into SQL text. The fix replaces the interpolated path with a bound parameter (`read_parquet(?)`) and moves the view names into a hardcoded, non-interpolated statement map — eliminating any path where filenames or directory values can alter SQL structure.

high

How JavaScript Injection via String Interpolation Happens in Go Wails Applications and How to Fix It

A high-severity JavaScript injection vulnerability in `internal/clusterconfigs/input.go` allowed arbitrary code execution through malicious kubeconfig filenames. The `saveClusterConfigFile` function at line 20 constructed JavaScript code by directly interpolating unsanitized filenames into `window.ExecJS()` calls, enabling attackers to break out of string literals and execute arbitrary JavaScript in the Webview context.

high

How Denial of Service via Prototype Pollution happens in Axios and how to fix it

Axios versions prior to 1.15.1 merged untrusted configuration objects without guarding against the `__proto__` key, letting attacker-controlled input pollute `Object.prototype` and crash or destabilize applications. Upgrading axios (and its transitive dependencies `form-data`, `follow-redirects`, `proxy-from-env`) closes this Denial of Service and prototype-pollution attack surface without changing any application code.

critical

How Server-Side Request Forgery happens in Node.js and how to fix it

The order-flow service in a Node.js e-commerce backend built an outbound fetch() URL by directly concatenating a configurable `sendingOrder.url` value with a query string, with no validation of protocol or destination. This allowed order data—including customer and payment-adjacent information—to be silently redirected to an attacker-controlled endpoint simply by changing a config value or environment variable.

high

How SQL Injection via Template Literals happens in Node.js and how to fix it

A high-severity SQL injection vulnerability was discovered in the `plugins/db-client/index.mjs` file where database queries were constructed using JavaScript template literals with dynamic input. The fix replaces vulnerable string interpolation with parameterized queries using MySQL2's `??` placeholder syntax, eliminating the injection vector entirely.