CodeAnt AI Security Research

One Malformed Zip File Crashes Your Node.js Server: Denial of Service in npm's Most Downloaded Zip Library (29M+ Weekly Downloads)

Amartya | CodeAnt AI Code Review Platform
Amartya Jha

CEO, CodeAnt AI


TL;DR

A single crafted zip file can crash any Node.js server that processes zip uploads. No authentication required. No user interaction. One request, one crash. The vulnerable library is yauzl, 29+ million weekly npm downloads, and the zip parsing layer behind VS Code, Electron, Puppeteer, electron-builder, and thousands of applications you use every day. The root cause? An off-by-one error in a timestamp parser introduced in v3.2.0. The fix is one character. Upgrade to v3.2.1 or later immediately.

Vulnerability at a Glance

Field

Value

Package

yauzl

Weekly Downloads

29,139,181

GitHub Stars

795

Affected Versions

3.2.0

Fixed Version

3.2.1

CVSS v3.1 Score

5.3 MEDIUM

CVSS Vector

AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L

CWE

CWE-193 (Off-by-One Error), CWE-125 (Out-of-bounds Read)

Discovered By

CodeAnt AI Code Reviewer

Acknowledged In

yauzl 3.2.1 changelog

What if a zip file containing 4 extra bytes of crafted metadata could crash your Node.js server?

That’s what we found in yauzl,the most widely-used zip parsing library in the Node.js ecosystem. yauzl is the library behind extract-zip, which is used by VS Code, Electron, Puppeteer, electron-builder, and thousands of other projects. If your Node.js application handles zip files, there’s a good chance yauzl is somewhere in your dependency tree.

An off-by-one error in the NTFS timestamp extra field parser, introduced in v3.2.0, allows a malformed zip file to trigger an unhandled ERR_OUT_OF_RANGE exception. The Node.js process crashes. No graceful error. No recovery. One zip file, one crash.

The patch is now live. If you use yauzl 3.2.0, stop reading and update:

Now, here’s how we found it.

Where This Started

We’ve been systematically auditing high-impact npm packages as part of our ongoing security research program at CodeAnt AI. The methodology is simple: take the most downloaded packages, run our AI code reviewer across recent changes, and look for patterns that rule-based tools miss.

When yauzl 3.2.0 shipped with new NTFS timestamp parsing support (PR #160), our AI reviewer flagged an anomaly in index.js at line 620: a while loop condition that allowed the cursor to exceed the buffer boundary by up to 4 bytes. The AI identified the semantic mismatch — the loop was checking cursor < data.length + 4 when it should have been checking cursor + 4 <= data.length. The off-by-one meant readUInt16LE() would attempt to read past the end of the buffer.

Our security engineer verified the flag, traced the execution path, built a minimal proof of concept, and confirmed: any server that accepts zip files and calls entry.getLastModDate() on entries with a crafted NTFS extra field will crash with an unhandled exception.

The entire analysis, from AI flag to confirmed DoS — took less than one hour.

The Root Cause: An Off-by-One in a New Feature

yauzl 3.2.0 added support for reading NTFS extended timestamp fields (extra field ID 0x000a). This was a welcome improvement, NTFS timestamps offer nanosecond precision compared to the 2-second precision of DOS timestamps.

The parser iterates over attribute tags inside the NTFS extra field data:

// index.js:620 — NTFS extra field parser in getLastModDate()
var cursor = 4;
while (cursor < data.length + 4) {   //BUG: allows cursor to exceed buffer by 4
  var tag  = data.readUInt16LE(cursor);  // OOB read when cursor >= data.length
  cursor += 2;
  var size = data.readUInt16LE(cursor);  // OOB read when cursor >= data.length
  cursor += 2;
  // ... process tag data ...
}
// index.js:620 — NTFS extra field parser in getLastModDate()
var cursor = 4;
while (cursor < data.length + 4) {   //BUG: allows cursor to exceed buffer by 4
  var tag  = data.readUInt16LE(cursor);  // OOB read when cursor >= data.length
  cursor += 2;
  var size = data.readUInt16LE(cursor);  // OOB read when cursor >= data.length
  cursor += 2;
  // ... process tag data ...
}
// index.js:620 — NTFS extra field parser in getLastModDate()
var cursor = 4;
while (cursor < data.length + 4) {   //BUG: allows cursor to exceed buffer by 4
  var tag  = data.readUInt16LE(cursor);  // OOB read when cursor >= data.length
  cursor += 2;
  var size = data.readUInt16LE(cursor);  // OOB read when cursor >= data.length
  cursor += 2;
  // ... process tag data ...
}

The condition cursor < data.length + 4 allows cursor to reach values up to data.length + 3. When cursor equals data.length, the readUInt16LE(cursor) call attempts to read 2 bytes starting at an offset beyond the buffer boundary. Node.js throws ERR_OUT_OF_RANGE. The process crashes.

The Fix: One Character

- while (cursor < data.length + 4) {
+ while (cursor + 4 <= data.length) {
- while (cursor < data.length + 4) {
+ while (cursor + 4 <= data.length) {
- while (cursor < data.length + 4) {
+ while (cursor + 4 <= data.length) {

That’s it. The corrected condition ensures the loop only continues when there are at least 4 bytes remaining in the buffer — enough to read both the tag and size fields without overflowing.

The fix was published as yauzl v3.2.1 with the changelog entry:

Fix crash when reading certain corrupted NTFS timestamp extra fields. Thanks to CodeAnt AI Code Reviewer.

The Proof of Concept

The vulnerability can be demonstrated directly with a minimal buffer:

// Direct demonstration of the buffer OOB read
const data = Buffer.alloc(4);  // NTFS extra field with 4 bytes of data
let cursor = 4;

while (cursor < data.length + 4) {         // cursor reaches 4 = data.length
  const tag = data.readUInt16LE(cursor);   // THROWS: offset 4 out of range [0, 2]
  cursor += 2;
  const size = data.readUInt16LE(cursor);
  cursor += 2;
}
// Direct demonstration of the buffer OOB read
const data = Buffer.alloc(4);  // NTFS extra field with 4 bytes of data
let cursor = 4;

while (cursor < data.length + 4) {         // cursor reaches 4 = data.length
  const tag = data.readUInt16LE(cursor);   // THROWS: offset 4 out of range [0, 2]
  cursor += 2;
  const size = data.readUInt16LE(cursor);
  cursor += 2;
}
// Direct demonstration of the buffer OOB read
const data = Buffer.alloc(4);  // NTFS extra field with 4 bytes of data
let cursor = 4;

while (cursor < data.length + 4) {         // cursor reaches 4 = data.length
  const tag = data.readUInt16LE(cursor);   // THROWS: offset 4 out of range [0, 2]
  cursor += 2;
  const size = data.readUInt16LE(cursor);
  cursor += 2;
}

We confirmed crashes for NTFS data lengths of 4, 8, and 11 bytes. Lengths of 12 or more bytes correspond to well-formed NTFS fields (which contain a 24-byte timestamp triplet) and do not crash.

The attack vector is any application that:

  1. Accepts zip file uploads from users

  2. Uses yauzl to parse those zip files

  3. Calls entry.getLastModDate() on the parsed entries

An attacker crafts a zip file with a single entry containing an NTFS extra field (0x000a) with a data payload of exactly 4, 8, or 11 bytes. When the application processes this file, the NTFS parser loop overflows, readUInt16LE() throws, and the Node.js process exits.

What the Attacker Can Do

Goal

Result

Crash a file upload endpoint

Single malformed zip file → unhandled exception → process exit

Denial of service on a CI/CD pipeline

Malicious zip in a repository → build system crashes on extraction

Disrupt an Electron application

Crafted zip file opened in app → renderer or main process crash

Repeated restarts

Automated uploads keep crashing the server on each restart

This is not remote code execution. It’s a denial-of-service vulnerability. But for any production server that handles zip files — file upload services, CI/CD pipelines, document processors, Electron apps — an unhandled crash is a real availability risk.

Blast Radius: 29 Million Weekly npm Downloads

yauzl is the dominant zip parsing library in the Node.js ecosystem:

Metric

Value

Weekly npm downloads

29,139,181

GitHub stars

795

npm dependents

450+ packages

GitHub dependent repositories

5,000+

But the raw numbers understate the impact. yauzl sits at the bottom of a deep dependency tree. The packages that depend on yauzl are themselves depended on by thousands more.

The Dependency Chain

yauzl’s most important direct dependent is extract-zip, which is the standard zip extraction utility in the Node.js ecosystem. And extract-zip is used by:

Project

GitHub Stars

How yauzl is used

microsoft/vscode

170,000+

Extension installation, update extraction

electron/electron

116,000+

App packaging and distribution

puppeteer

89,000+

Chromium binary download and extraction

electron-builder

13,000+

Build artifact packaging

@vscode/test-electron

VS Code extension test framework

Any application built on Electron — VS Code, Slack Desktop, Discord, Figma Desktop, Notion Desktop — has yauzl somewhere in its dependency tree. Any CI/CD pipeline that extracts zip artifacts likely uses yauzl through extract-zip or a similar wrapper.

Why Traditional Tools Miss This

We tested the major security tools against this vulnerability:

Tool

Detects this vulnerability?

Why?

CodeAnt AI

Yes

AI analysis of new code for semantic buffer safety issues

Snyk

No ❌

No advisory in Snyk vulnerability database

Semgrep

No ❌

No rule for off-by-one in buffer loop conditions

SonarQube

No ❌

No rule for Node.js buffer boundary validation in while loops

ESLint

No ❌

No security-aware buffer bounds checking

The gap here is different from our previous CVE disclosures. This isn’t a case where the advisory hasn’t propagated yet. This is a case where the vulnerability is in new code — code that was just shipped in a feature PR, reviewed by the maintainer, and released. No rule-based tool has a pattern for “while loop condition allows buffer cursor to exceed bounds by a constant.”

Our AI code reviewer catches this because it understands what the code is doing, not just what it looks like. A loop that iterates over a buffer and reads fixed-width fields must ensure the cursor stays within bounds at every iteration. The AI recognises when the arithmetic in the loop condition doesn’t guarantee this — regardless of whether anyone has ever written a Semgrep rule for this specific pattern.

A Note on yauzl and Josh Wolfe

yauzl is one of those packages that the entire Node.js ecosystem relies on without most developers knowing it exists. It’s a library that does one thing — parse zip files — and does it with obsessive correctness. Josh Wolfe has maintained it for over a decade, with meticulous adherence to the zip file specification and careful attention to security (yauzl’s validateEntrySizes and validateFileName features are exemplary).

When we reported this vulnerability, Josh acknowledged it within days and published the fix promptly. The changelog credits our team by name. That’s the kind of professionalism that keeps the open-source ecosystem running.

We said this last week about Steve and simple-git, and the week before about Jérôme and pac4j: open-source maintainers carry the weight of the modern software supply chain. If your company depends on yauzl — and if you use Node.js, it almost certainly does — consider supporting the maintainer.

Josh, thank you.

Timeline

Date

Event

February 28, 2026

Vulnerability flagged by CodeAnt AI code reviewer; confirmed by security engineer

March 1, 2026

Reported to maintainer (Josh Wolfe) via email

March 8, 2026

Follow-up sent

March 10, 2026

Maintainer acknowledges the bug

March 10, 2026

Fix published as v3.2.1 with CodeAnt AI credited in changelog

Are You Affected?

Step 1: Check if you depend on yauzl

npm ls
npm ls
npm ls

If you see version 3.2.0, you are affected. Earlier versions (2.x) are not affected — the NTFS parser was introduced in 3.2.0.

Step 2: Check your exposure

You’re at risk if your application:

  • Accepts zip file uploads from untrusted sources

  • Extracts zip files in CI/CD pipelines

  • Calls entry.getLastModDate() on parsed zip entries

  • Uses extract-zip or any wrapper that calls getLastModDate() internally

Step 3: Update

npm
npm
npm

Verify:

npm ls yauzl
# Should show 3.2.1 or higher
npm ls yauzl
# Should show 3.2.1 or higher
npm ls yauzl
# Should show 3.2.1 or higher

If you cannot upgrade immediately, wrap getLastModDate() calls in a try/catch to prevent the unhandled exception from crashing your process.

Three Vulnerabilities in 1 Weeks

We published CVE-2026-29000, a complete authentication bypass in pac4j-jwt, CVE-2026-28292,a CVSS 9.8 RCE in simple-git where changing one letter to uppercase bypasses two prior security patches. This week, an OOB read in yauzl that crashes any Node.js server processing malformed zip files.

Three different ecosystems. Three different vulnerability classes. Three different severity levels. Same research program. Same AI code reviewer.

The pattern is consistent: whether it’s a bypass of an existing security control, a flaw in new feature code, or a semantic mismatch between a regex and the system it protects — our AI finds what rule-based tools don’t. It doesn’t pattern-match against known CVEs. It reasons about whether the code actually does what it’s supposed to do.

A while loop that reads 4 bytes per iteration but checks cursor < data.length + 4 instead of cursor + 4 <= data.length is not a known vulnerability pattern. It’s a logic error. That’s what AI catches and static analysis rules don’t.

This research is part of an ongoing effort by CodeAnt AI Security Research to audit widely-used open-source packages for security vulnerabilities. We believe the open-source ecosystem deserves better tools, better auditing, and more support for the maintainers who keep it running. More findings will be published as patches ship and coordinated disclosure timelines are met.

If you are a maintainer and have been contacted by our team — thank you for your work. If you believe your package may be affected by a similar pattern, we’d love to help: securityresearch@codeant.ai

FAQs

What is this vulnerability?

What versions of yauzl are affected?

Who discovered this vulnerability?

How do I check if my application is vulnerable?

How do I fix it?

Is this as severe as CVE-2026-28292 (simple-git) or CVE-2026-29000 (pac4j)

Table of Contents