Back to Blog
high SEVERITY9 min read

How Denial of Service via ZIP Parsing 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 (versions before 0.6.0) that allows an attacker to cause excessive memory allocation by supplying a specially crafted ZIP file. The vulnerability was present in the `dsh-mneme` component of the project and was remediated by upgrading `adm-zip` from `0.5.18` to `0.6.0`. Left unpatched, this flaw could allow any user capable of uploading or supplying ZIP input to crash or severely degrade the Node.js ser

O
By Orbis AppSec
Published August 16, 2026Reviewed August 16, 2026

Answer Summary

CVE-2026-39244 is a Denial of Service vulnerability (CWE-400: Uncontrolled Resource Consumption) in the `adm-zip` npm package versions prior to 0.6.0. An attacker can supply a crafted ZIP archive that causes the library to allocate excessive memory during parsing, potentially crashing or hanging the Node.js process. The fix is to upgrade `adm-zip` to version 0.6.0 or later, which adds proper validation of ZIP metadata fields before allocating memory buffers. In this project, the dependency was updated in `dsh-mneme/package-lock.json` and `dsh-mneme/package.json`.

Vulnerability at a Glance

cweCWE-400 (Uncontrolled Resource Consumption)
fixUpgrade adm-zip from 0.5.18 to 0.6.0 in dsh-mneme/package-lock.json and package.json
riskAttacker-supplied ZIP file crashes or hangs the Node.js process
languageJavaScript / Node.js
root causeadm-zip <0.6.0 trusts ZIP metadata size fields without bounds validation before allocating memory
vulnerabilityDenial of Service via crafted ZIP file (excessive memory allocation)

How Denial of Service via ZIP Parsing Happens in Node.js and How to Fix It


Vulnerability at a Glance

Field Detail
CVE CVE-2026-39244
Severity High
Package adm-zip < 0.6.0
CWE CWE-400 – Uncontrolled Resource Consumption
Language JavaScript / Node.js
Fixed in adm-zip 0.6.0

Introduction

The dsh-mneme component of this project handles ZIP file processing through the popular adm-zip npm package. A high-severity vulnerability — CVE-2026-39244 — was discovered in adm-zip versions prior to 0.6.0 that allows any party capable of supplying a ZIP file to the application to trigger uncontrolled memory allocation, potentially crashing the Node.js process entirely.

This is not a subtle logic flaw buried deep in application code. It lives in a widely-used third-party library that is a transitive or direct dependency recorded in dsh-mneme/package-lock.json. The fix is a one-version upgrade, but understanding why this class of vulnerability is dangerous — and how ZIP files can be weaponized — is essential knowledge for any Node.js developer working with file uploads or archive processing.


The Vulnerability Explained

How ZIP Parsing Works (and Where It Can Go Wrong)

A ZIP archive is not just a compressed blob. It contains a structured Central Directory at the end of the file, which is a table of metadata entries describing each file inside the archive: its name, compressed size, uncompressed size, offset in the file, and more. A ZIP parser typically reads this directory first to understand what's inside before decompressing anything.

The critical trust boundary is here: the parser must not blindly trust the size fields in the Central Directory. Those fields are written by whoever created the ZIP file. An attacker can craft a ZIP archive where a metadata field claims an entry's uncompressed size is, say, 4 gigabytes — even though the actual compressed data is only a few bytes. If the parser allocates a buffer of that claimed size before reading the compressed data, memory is exhausted instantly.

The Vulnerable Pattern in adm-zip < 0.6.0

In versions of adm-zip prior to 0.6.0, the library's entry-reading logic did not adequately validate the size values declared in ZIP Central Directory headers against the actual file size or configurable limits before allocating output buffers. The vulnerable behavior looks conceptually like this:

// Simplified illustration of the vulnerable pattern in adm-zip < 0.6.0
function readEntry(entry) {
  const uncompressedSize = entry.header.size; // Attacker-controlled value
  const buffer = Buffer.alloc(uncompressedSize); // Allocated WITHOUT bounds check
  // ... decompress into buffer
}

The entry.header.size field comes directly from the ZIP file's metadata. In adm-zip 0.5.18 (the version pinned in dsh-mneme/package-lock.json before this fix), that value was used to allocate a Buffer without first verifying it was reasonable relative to the actual compressed data or any system limit.

Attack Scenario

Consider a dsh-mneme service endpoint that accepts ZIP file uploads — for example, a batch data import feature. An attacker constructs a ZIP file that is only a few kilobytes on disk but declares an uncompressed entry size of 2 GB in its Central Directory. When the Node.js process calls adm-zip to read the archive:

  1. adm-zip reads the Central Directory and encounters the 2 GB size claim.
  2. It calls Buffer.alloc(2_000_000_000) (or equivalent) to prepare the output buffer.
  3. The Node.js process attempts to allocate 2 GB of RAM.
  4. The process either crashes with an out-of-memory error or the host system begins swapping, degrading all other services.
  5. The attacker repeats this with a handful of concurrent requests to guarantee a full outage.

This attack requires no authentication if the upload endpoint is public, and only minimal authentication bypass if it is protected — making it a realistic threat for any web service that processes user-supplied ZIP files.

Real-World Impact for dsh-mneme

The dsh-mneme component's package-lock.json locked adm-zip at version 0.5.18. Any code path in dsh-mneme that calls adm-zip APIs such as readFile(), getEntries(), or extractAllTo() on untrusted input was potentially reachable by this attack. While the PR assessment notes the path was "not confirmed reachable," the presence of a vulnerable version in the dependency tree is sufficient risk to warrant immediate remediation — especially for a High-severity CVE.


The Fix

What Changed

The fix upgrades adm-zip from 0.5.18 to 0.6.0 in two files:

  • dsh-mneme/package.json — updates the declared version range
  • dsh-mneme/package-lock.json — pins the resolved version and updates the integrity hash

The package-lock.json diff also removes a series of "libc" constraint fields from optional platform-specific binary dependencies (entries for architectures like arm, arm64, ppc64, riscv64, s390x, x64 under both glibc and musl variants). This is a metadata cleanup that accompanied the version bump in the lock file regeneration — these "libc" fields were removed because the updated lock file format no longer requires them for platform resolution.

Before and After

Before (dsh-mneme/package-lock.json — adm-zip 0.5.18):

"adm-zip": {
  "version": "0.5.18",
  "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.18.tgz",
  ...
}

After (dsh-mneme/package-lock.json — adm-zip 0.6.0):

"adm-zip": {
  "version": "0.6.0",
  "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.6.0.tgz",
  ...
}

How Version 0.6.0 Fixes the Problem

adm-zip 0.6.0 introduces validation of ZIP entry metadata fields before memory allocation. Specifically, it checks that declared entry sizes are consistent with the actual archive file size and enforces limits that prevent absurdly large buffer allocations from crafted headers. This means a ZIP file claiming a 2 GB uncompressed entry will be rejected early in parsing — before any large allocation occurs — rather than causing the process to run out of memory.

The fix is entirely backward-compatible for legitimate ZIP files: valid archives with accurate metadata are processed identically. Only malformed or malicious archives with inflated size fields are now rejected.

The libc Field Removals

The diff shows removal of "libc": ["glibc"] and "libc": ["musl"] entries from multiple optional platform binary entries, for example:

-      "libc": [
-        "glibc"
-      ],
       "license": "LGPL-3.0-or-later",
       "optional": true,

This is a lock file normalization change introduced when regenerating package-lock.json with npm's updated resolution algorithm. These fields were used in some npm versions to further constrain which optional binary to install based on the system's C library. Their removal does not affect security — it reflects a cleaner lock file format that relies on the os and cpu fields alone for platform selection.


Prevention & Best Practices

1. Pin and Audit Your Lock Files

The vulnerability was detected because package-lock.json explicitly pinned adm-zip to 0.5.18. Lock files are your first line of defense — they make it possible for scanners like Trivy to identify exactly which vulnerable version is in use.

# Audit your npm project for known vulnerabilities
npm audit

# Use Trivy to scan your lock file directly
trivy fs --scanners vuln dsh-mneme/package-lock.json

2. Validate ZIP Input at the Application Layer

Even with a patched library, defense in depth means validating archives before passing them to any parser:

const MAX_ZIP_SIZE_BYTES = 50 * 1024 * 1024; // 50 MB limit
const MAX_ZIP_ENTRIES = 1000;

function safeExtract(zipBuffer) {
  if (zipBuffer.length > MAX_ZIP_SIZE_BYTES) {
    throw new Error('ZIP file exceeds maximum allowed size');
  }
  const zip = new AdmZip(zipBuffer);
  const entries = zip.getEntries();
  if (entries.length > MAX_ZIP_ENTRIES) {
    throw new Error('ZIP file contains too many entries');
  }
  // Validate each entry's claimed uncompressed size
  for (const entry of entries) {
    if (entry.header.size > MAX_ZIP_SIZE_BYTES) {
      throw new Error(`Entry ${entry.entryName} claims excessive uncompressed size`);
    }
  }
  return entries;
}

3. Set Node.js Memory Limits

Use Node.js's --max-old-space-size flag to cap the heap, and run services in containers with memory limits. This won't prevent the DoS entirely but limits blast radius:

node --max-old-space-size=512 server.js

4. Enable Automated Dependency Scanning

Integrate vulnerability scanning into your CI/CD pipeline so that vulnerable dependencies are caught before they reach production:

# Example GitHub Actions step
- name: Run Trivy vulnerability scanner
  uses: aquasecurity/trivy-action@master
  with:
    scan-type: 'fs'
    scan-ref: '.'
    severity: 'HIGH,CRITICAL'

5. Security Standards Reference

  • OWASP A06:2021 – Vulnerable and Outdated Components: This vulnerability is a textbook example of the risk of using unpatched third-party libraries.
  • CWE-400: Uncontrolled Resource Consumption — the root cause category for this class of ZIP bomb / memory exhaustion attack.
  • CWE-770: Allocation of Resources Without Limits or Throttling — the specific allocation pattern exploited here.

Key Takeaways

  • adm-zip versions before 0.6.0 trust ZIP Central Directory size fields without bounds checking — a single crafted upload can exhaust Node.js heap memory.
  • The dsh-mneme/package-lock.json lock file pinned the vulnerable 0.5.18 version, which is exactly why lock file scanning with tools like Trivy is essential — it catches the precise version in use.
  • Upgrading to adm-zip 0.6.0 is the complete fix — no application code changes are needed; the library itself now validates metadata before allocation.
  • ZIP-based DoS attacks require no decompression — the damage happens at the header-reading stage, making even "read-only" archive inspection code vulnerable.
  • Defense in depth matters: combine library patching with application-layer size limits and container memory caps to minimize the impact of any future similar vulnerabilities.

How Orbis AppSec Detected This

  • Source: A ZIP file supplied via user-controlled input to any dsh-mneme code path that invokes adm-zip APIs (e.g., new AdmZip(userSuppliedBuffer) or readFile(userPath)).
  • Sink: adm-zip's internal buffer allocation during ZIP Central Directory parsing — specifically, the Buffer.alloc() call sized from an unvalidated entry.header.size field in adm-zip 0.5.18.
  • Missing control: No bounds validation on ZIP entry size metadata fields before memory allocation; no maximum allocation limit enforced by the library.
  • CWE: CWE-400 – Uncontrolled Resource Consumption.
  • Fix: Upgraded adm-zip from 0.5.18 to 0.6.0 in dsh-mneme/package-lock.json and dsh-mneme/package.json, which introduces 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 sharp reminder that the attack surface of a Node.js application extends well beyond the code your team writes. A single unpatched npm dependency — in this case adm-zip 0.5.18 in dsh-mneme — can expose your entire service to a high-severity Denial of Service attack that requires nothing more than a malformed ZIP file. The fix is straightforward: upgrade to adm-zip 0.6.0. But the broader lesson is to treat your package-lock.json as a security artifact, scan it continuously, and apply patches promptly when vulnerabilities in dependencies are disclosed.


References

Frequently Asked Questions

What is a Denial of Service via ZIP parsing vulnerability?

It occurs when a ZIP parsing library trusts attacker-controlled size or count fields in a ZIP archive's metadata, causing it to allocate enormous memory buffers before reading any actual data, exhausting available memory and crashing the process.

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

Always use a patched version of your ZIP library, validate entry sizes against configurable limits before allocation, and consider enforcing maximum file size and entry count limits at the application layer before passing data to the parser.

What CWE is ZIP-based Denial of Service?

CWE-400 (Uncontrolled Resource Consumption), sometimes also associated with CWE-770 (Allocation of Resources Without Limits or Throttling).

Is rate limiting enough to prevent this type of DoS?

Rate limiting reduces attack frequency but does not prevent a single malicious ZIP from consuming all available memory in one request. Input validation inside the parsing library is the essential fix.

Can static analysis detect this vulnerability?

Yes — vulnerability scanners like Trivy can detect known-vulnerable package versions in lock files. In this case, Trivy flagged adm-zip 0.5.18 via rule CVE-2026-39244 in dsh-mneme/package-lock.json.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #5

Related Articles

critical

How Command Injection via Unescaped Line Terminators Happens in Node.js and How to Fix It

A critical command injection vulnerability (CVE-2026-9277) was discovered in the shell-quote npm package version 1.8.3, where unescaped line terminators could allow attackers to execute arbitrary code. This fix upgrades shell-quote to version 1.9.0 using npm overrides to ensure all instances in the dependency tree are patched, eliminating the attack vector across the entire application.

critical

How Distributed Lock Takeover Happens in Node.js and How to Fix It

A critical vulnerability in `redis-lock/server.mjs` allowed any authenticated client to release another client's lock by guessing predictable holder identifiers like process IDs or hostnames. The fix implements cryptographically random `lockId` values that are minted on lock acquisition and validated on release, eliminating the exploit primitive entirely.

high

How Denial of Service via Infinite Loop happens in JavaScript (nanoid) and how to fix it

A high-severity denial of service vulnerability (CVE-2026-67213) was discovered in nanoid versions before 5.1.6 and 3.3.18, where the `customAlphabet` function could enter an infinite loop during random ID generation. The fix upgrades the transitive nanoid dependency from 3.3.16 to 3.3.18 using pnpm overrides, ensuring the vulnerable code path is eliminated from the entire dependency tree including PostCSS.

high

How Information Disclosure via Unstripped Credential Headers Happens in Electron Apps and How to Fix It

A high-severity vulnerability (CVE-2026-54673) in the builder-util-runtime package allowed sensitive credential headers to leak during HTTP redirects in Electron applications. The fix upgrades builder-util-runtime from version 9.5.1 to 9.7.0, which properly strips authentication headers before following redirects to prevent information disclosure.

high

How Command Injection happens in PHP and how to fix it

A high-severity command injection vulnerability was discovered in `lib/Controller/Helper.php` where the `corruptline()` method used `exec()` to run sed and awk commands with user-controlled input. The fix replaced all shell command execution with native PHP file operations using `SplFileObject`, eliminating the command injection attack surface entirely.

high

How Missing CSRF Middleware happens in Express.js and how to fix it

A high-severity CSRF vulnerability was discovered in `libProxy.js` of an Express.js application — the app had no CSRF middleware protecting its state-changing routes, leaving them open to cross-site request forgery attacks. The fix introduces a `csrf` token library, a `/csrf-token` endpoint to issue tokens, and a middleware that validates `x-csrf-token` headers or `_csrf` body fields on all non-safe HTTP methods. This proactive hardening removes an exploit primitive that could be chained with ot