Back to Blog
high SEVERITY3 min read

KNX Project Extractor ZIP Bomb: Unbounded Decompression Before Size

The KNX project extractor used `@zip.js/zip.js` to decompress .knxproj files without enforcing maximum entry sizes, total archive sizes, or compression ratios. This allowed attackers to upload ZIP bombs that expanded exponentially—like the famous 42.zip producing 4.5PB from 42KB—consuming all available memory before the existing `Checked` validation could trigger. The fix introduces three hard limits: 512MB per entry, 1GB total per archive, and a 100:1 compression ratio ceiling.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published September 25, 2026•Reviewed September 25, 2026

Answer Summary

The `extract()` function in the KNX project processing library accepted arbitrary .knxproj archives without pre-decompression size validation. An attacker could upload a ZIP bomb with extreme compression ratios—such as 100 million to one—to exhaust server memory and CPU, causing denial of service before any size check executed. The fix adds `MAX_ENTRY_UNCOMPRESSED_SIZE`, `MAX_TOTAL_UNCOMPRESSED_SIZE`, and `MAX_COMPRESSION_RATIO` guards at the start of extraction. CWE-409 (Improper Handling of Highly Compressed Data).

Vulnerability at a Glance

cweCWE-409
fixHard limits on entry size, total size, and compression ratio before decompression begins
riskDenial of service via memory exhaustion from malicious .knxproj uploads
languageJavaScript (Node.js)
root cause`ZipReader` decompression without pre-size validation or compression ratio limits
vulnerabilityZIP bomb / decompression bomb

Introduction

A ZIP bomb reached production in the KNX project extraction path, where extract() processed .knxproj archives through @zip.js/zip.js without guarding against exponential decompression. The ZipReader class dutifully inflated whatever data it received, and only afterward did the Checked wrapper validate sizes—far too late to prevent memory exhaustion from a crafted payload.

This is CWE-409: Improper Handling of Highly Compressed Data. The vulnerability sits in the gap between "start decompression" and "verify the result isn't absurdly large." That gap, measured in CPU cycles and heap allocations, is where ZIP bombs detonate.

Affected Versions

Affected not applicable (first-party code)
Fixed in not applicable (first-party code)
Ecosystem npm
CVE / GHSA not assigned
CWE CWE-409 (Improper Handling of Highly Compressed Data)

The vulnerable code processed .knxproj files in Node.js environments using @zip.js/zip.js version 2.x without additional guards.

The Vulnerability Explained

The extraction routine configured ZipReader with Checked output validation:

const { BlobReader, ZipReader, Uint8ArrayWriter, configure } = require("@zip.js/zip.js");
// Disable web workers — we run in Node.js
configure({ useWebWorkers: false });

The Checked class wraps writers to enforce size limits after writing completes. But decompression happens inside ZipReader.getData()—memory allocates, CPU burns, and the event loop stalls before Checked ever sees the inflated bytes.

An attacker uploads evil.knxproj: a 10KB file containing nested ZIP archives with identical files, each layer referencing the next. The outermost file reports modest compressed sizes. When extract() calls getData(), the library recursively inflates through 16 layers, producing gigabytes of identical null bytes. The Node.js process heap grows until the OS OOM killer intervenes—or slower peers timeout, whichever comes first.

The famous 42.zip achieves 4.5 petabytes from 42 kilobytes. Against unguarded code, that's not a theoretical concern; it's a single HTTP POST away.

The Fix

The patch introduces three hard limits before any decompression begins:

// Limits to guard against ZIP bomb attacks (CWE-409)
const MAX_ENTRY_UNCOMPRESSED_SIZE = 512 * 1024 * 1024; // 512 MB per entry
const MAX_TOTAL_UNCOMPRESSED_SIZE = 1024 * 1024 * 1024; // 1 GB total per archive
const MAX_COMPRESSION_RATIO = 100; // reject entries that inflate more than 100x

These constants enable pre-flight validation. Before ZipReader inflates an entry, the code now:

  1. Checks compression ratio: compressedSize * MAX_COMPRESSION_RATIO < declaredUncompressedSize triggers immediate rejection
  2. Enforces per-entry limits: Individual entries exceeding 512MB abort extraction
  3. Tracks aggregate size: Running total across all entries cannot exceed 1GB

The 100:1 ratio catches 42.zip's 100-billion-to-one attack with margin to spare, while legitimate KNX projects—typically tens of megabytes with modest compression—pass untouched.

Key Takeaways

  • Post-decompression validation is too late: Size checks must precede getData() or equivalent calls; memory pressure manifests during inflation, not after
  • Compression ratios reveal attacks: Legitimate archives rarely exceed 10:1; thresholds at 100:1 catch bombs without false positives
  • Aggregate limits matter: ZIP bombs nest—per-entry caps alone won't stop 10,000 50MB entries from exhausting resources
  • @zip.js/zip.js defaults are permissive: The library trusts input; security boundaries must be enforced by calling code
  • KNX project files are attack surface: Industrial automation formats processed server-side warrant the same scrutiny as user-uploaded images

How Orbis AppSec Detected This

Source: The buffer parameter passed to extract() from HTTP request bodies containing .knxproj uploads

Sink: ZipReader.getData() invoked without pre-decompression size validation or compression ratio checks

Missing control: No enforcement of MAX_ENTRY_UNCOMPRESSED_SIZE, MAX_TOTAL_UNCOMPRESSED_SIZE, or MAX_COMPRESSION_RATIO before decompression begins; reliance on Checked class providing only post-hoc validation

CWE: CWE-409 — Improper Handling of Highly Compressed Data

Fix: Added three hard limits (512MB per entry, 1GB total, 100:1 ratio) validated before ZipReader processes archive contents

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

ZIP bombs exploit the fundamental asymmetry between compression and decompression: cheap to create, expensive to process. The KNX project extractor learned this lesson the hard way—Checked validation arrived after the damage was done. The fix demonstrates proper defense: assume malicious input, validate before expensive operations, and never trust compression ratios at face value. For any service accepting archives, these three limits—per-entry, total, and ratio—are the minimum viable security boundary.

Prevention and further reading

Frequently Asked Questions

Does the fix change how valid .knxproj files are processed, or only reject malicious ones?

Valid files under 512MB per entry, 1GB total, and 100:1 compression ratio process identically. Only archives exceeding these thresholds—characteristic of ZIP bombs—are rejected.

Why was the `Checked` class from `@zip.js/zip.js` insufficient protection?

`Checked` validates size *after* decompression completes. A ZIP bomb like 42.zip expands from 42KB to 4.5PB during inflation, exhausting memory before any post-check can abort the operation.

Is the 100:1 `MAX_COMPRESSION_RATIO` sufficient for legitimate KNX project files?

Yes. Typical KNX project files use modest compression; ratios above 100:1 indicate either pathological data or deliberate attack. This threshold catches 42.zip's 100-billion-to-one ratio with substantial margin.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #760

Related Articles

critical

Lampa Desktop Auto-Update Heuristic Bypass: Execution of Unverified

Lampa Desktop's auto-update mechanism downloaded JavaScript and CSS from `raw.githubusercontent.com` using only heuristic validation—file size thresholds and string pattern matching—that attackers could trivially satisfy. The fix introduces cryptographic integrity verification by cross-referencing Git blob hashes from the GitHub Contents API, ensuring downloaded code matches the repository's authoritative state before execution.

high

adm-zip 0.6.0 Preserves SUID Bits From ZIPs: CVE-2026-102282

The `adm-zip` dependency resolved to 0.6.0 in this project's dependency tree, a version affected by CVE-2026-102282: during extraction it applies the Unix permission bits stored in each ZIP entry's external file attributes verbatim, including the setuid (`04000`), setgid (`02000`), and sticky bits. An attacker who controls an archive passed to `extractAllTo()` or `extractEntryTo()` can therefore have the extractor create a setuid binary owned by whatever user the extraction process runs as. The

high

requestInput() Type Confusion: NaN and Object Bypass in JavaScript

The `requestInput()` utility function lacked validation on its `type` parameter and failed to handle `NaN` results from float conversions, creating a type confusion weakness. An attacker could supply malformed inputs that propagate unhandled `NaN` values or unexpected object types through the type system. The fix adds explicit guards against `NaN` type parameters and rejects non-primitive type values.

critical

No Rate Limit on /api/uploads/presign Enables DoS

The `/api/uploads/presign` endpoint accepted unlimited concurrent requests to generate storage presigned URLs, giving an attacker a free lever to exhaust storage-provider quotas and server resources. The fix adds an `express-rate-limit` middleware capping each client to 30 requests per minute on that route.

high

CVE-2026-54673: builder-util-runtime Leaks Auth Headers on Redirect

electron-updater and electron-builder rely on builder-util-runtime to fetch update manifests and artifacts over HTTP. A flaw in that shared HTTP executor allowed credential headers attached to the original update-feed request to be re-sent after a redirect, exposing them to any host the redirect pointed to. The project fixes this by upgrading builder-util-runtime to 9.7.0 and collapsing a duplicate, older copy of the package that electron-updater had pinned on its own.

high

image-size 1.2.1 DoS: Zero-Valued Dimensions in Image Buffer Parser

A high-severity denial-of-service vulnerability in image-size 1.2.1 allows attackers to crash Node.js services using malicious image buffers with zero-valued dimensions. The fix removes the vulnerable `queue` dependency and tightens dimension validation in version 2.0.3.