~/f4n6 $ grep -r "adm-zip: Decompression-bomb protection (fix for CVE-2026-39244) can be bypassed by declaring uncompressed size as 0" ./investigations/ --include="*.md"

adm-zip: Decompression-bomb protection (fix for CVE-2026-39244) can be bypassed by declaring uncompressed size as 0

Jeff Davies 29 Sep 2026 8 min read

1. Executive summary

A bypass has been published for the decompression-bomb fix in adm-zip 0.6.0, the npm archive library. The fix for CVE-2026-39244 (CVSS 7.5 HIGH, CWE-400, not in CISA KEV, EPSS 1%) caps zlib output using the ZIP entry's declared uncompressed size, but only when that declared size is greater than zero; setting the field to exactly 0 removes the cap entirely and lets a small, highly compressible entry expand to a multi-gigabyte buffer in memory. Any Node.js service that reads or extracts untrusted ZIP files through adm-zip is exposed to process crash or OOM, and the affected code path is shared by every read and extract method. The practical risk to EMEA financial services is availability, not data compromise: upload handlers, CI artifact extraction and email attachment scanning are the exposed surfaces. No patch version is named in the source material, so the interim control is an application-level size and ratio limit in front of adm-zip.

2. Regulatory framing

Article Trigger (the fact in this item) Practical impact
DORA Art. 17 The bypass is reachable through upload handlers, CI artifact extraction and email attachment scanning, so an exploited instance is an ICT-related incident that must be handled under the firm's incident management process. The process must cover detection, containment and root-cause for an availability incident in a Node.js service, not only for network intrusions.
DORA Art. 18 The failure mode is process crash or OOM from a crafted input, which is a classification question (availability impact, no data loss) rather than a breach. Firms need the classification criteria applied to availability-only incidents so reporting thresholds are decided consistently.
NIS2 Art. 21(2)(d) adm-zip is a third-party npm dependency embedded in the firm's own services, so the exposure sits in the supply chain. Supply chain security measures must cover the vetting and monitoring of this dependency and its version pinning.

DORA Art. 19 and NIS2 Art. 23 are not engaged by this item on its own: they attach to a major incident or a significant incident once one has occurred, and nothing in the source material establishes that. UK NIS 2018 duties apply only where the affected service falls within the OES/RDSP scope defined by that regime; the source does not identify any such service.

3. Technical analysis & attack chain

The vulnerability is a bypass of the fix shipped for CVE-2026-39244, not a new flaw in the original code. The affected version is adm-zip 0.6.0. The fix lives in methods/inflater.js and caps zlib's decompression output by passing maxOutputLength: expectedLength, where expectedLength is read directly from the ZIP entry's attacker-controlled uncompressed-size header field (CENLEN in the central directory, LOCLEN in the local file header). The cap is applied conditionally:

const option = version >= 15 && expectedLength > 0 ? { maxOutputLength: expectedLength } : {};
return zlib.inflateRawSync(inbuf, option);

When the declared uncompressed size is exactly 0, the expectedLength > 0 test is false, option becomes {}, and no output cap reaches zlib. Node then falls back to zlib's own internal default limit, which the source describes as several GB. The result is the same resource-exhaustion class the original CVE addressed, reached by a different trigger.

Confirmed attack chain, from the advisory's reproduction steps:

  1. Build a ZIP archive containing one DEFLATE-compressed entry whose real content is highly redundant, for example several MB of a repeated byte, approaching the roughly 1032:1 theoretical raw-DEFLATE compression ratio.
  2. Patch the entry's declared uncompressed-size fields to 0. Both copies must be changed: the local file header copy and the central directory copy, each a 4-byte little-endian value. The compressed bytes and the CRC32 are left untouched.
  3. Deliver the archive to an application that calls adm-zip's read or extract methods on untrusted input.
  4. The application loads it with new AdmZip(buffer) and calls .getEntries()[0].getData(), or readFile, readAsText, extractAllTo, or any other read or extract method. All of these share the same code path.
  5. Decompression succeeds and returns the full-size buffer with no size restriction applied.

The CRC32 check does not stop this. CRC is computed over the real decompressed output, not over the declared size, so leaving the CRC untouched means validation still passes. The advisory's controlled A/B test makes the mechanism explicit: with the declared size set to 1024 bytes, deliberately smaller than the true 4 MB output, the call correctly throws Cannot create a Buffer larger than N bytes, proving the cap works when expectedLength > 0. With the declared size set to 0 and an identical real payload, the call succeeds and returns the full 4,194,304-byte buffer with no restriction. The advisory states this was reproduced across three independent runs.

Impact is bounded only by zlib's internal default rather than by any limit the application or adm-zip intends. The source states that a small upload in the tens of MB can trigger multi-GB memory consumption, risking process crash or OOM. Exposed surfaces named in the source are upload handlers, CI artifact extraction and email attachment scanning. There is no code execution, no persistence, no command-and-control and no data access described in this item; the effect is availability loss.

Related adm-zip resource-exhaustion issues in the same code area are documented separately and are worth noting because they share the root cause of trusting the declared size. GHSA-xcpc-8h2w-3j85 covers adm-zip before 0.5.18, where zipEntry.js line 103 calls Buffer.alloc(_centralHeader.size) using the declared uncompressed size with no validation against the compressed data and no upper bound, with the value read at entryHeader.js line 266. That advisory describes a roughly 120-byte ZIP declaring about 4 GB uncompressed, an amplification above 33 million to one, with the allocation occurring before CRC validation. GHSA-7q85-xj36-vmfc covers adm-zip 0.5.17 on Node 24, where a 105-byte ZIP with one stored entry declaring size 1,774,399,200 commits about 1.8 GB of resident memory in roughly 4.4 seconds before throwing Error: ADM-ZIP: CRC32 checksum failed, roughly 16 million times the input size. Those two advisories are separate from CVE-2026-39244 and from the bypass described here.

Confidence caveats. The bypass detail, the reproduction steps and the A/B result all come from a single advisory, GHSA-rcw4-f5rp-g42v, and the related advisories are the same class of source. No independent exploitation reporting is present in the material. The source names no fixed version for the bypass, so treat any claim that a specific release resolves it as unverified until the maintainer publishes one. The source also does not state whether the bypass has been exploited in the wild.

4. Mitigation & containment

P1, within 24 hours:

  • Inventory every service that depends on adm-zip, including transitive dependencies, and record the resolved version. The bypass is specific to 0.6.0; the related advisories affect versions before 0.5.18 and 0.5.17.
  • Enforce an application-level cap on decompressed output before adm-zip is called. Reject any archive whose declared uncompressed size is 0 for a DEFLATE entry, and reject any entry whose declared size exceeds a configured ceiling or whose declared-to-compressed ratio exceeds a configured threshold. This is the control the source's suggested fix describes, and it does not depend on a patched library.
  • Where the service does not need to read untrusted archives, disable the adm-zip read and extract path for that input class or route the input to a parser that requires the caller to bound the output.

P2, within 72 hours:

  • Apply the source's suggested fix if you maintain a fork: pass maxOutputLength unconditionally, defaulting to a sane absolute ceiling, or always pass the declared size regardless of whether it is 0, and add an independent compression-ratio check that does not rely solely on the attacker-supplied size field.
  • Cap the memory available to the Node.js process handling archive input, for example with a container memory limit and a Node heap ceiling, so a single crafted archive cannot take down a shared host. The source notes that on memory-constrained hosts the allocation OOM-kills the process before the CRC check and that the kill is uncatchable.
  • Rate-limit and queue archive-processing endpoints so concurrent crafted uploads cannot exhaust memory on larger hosts.

P3, within 7 days:

  • Add a regression test that feeds an archive with the declared uncompressed size set to 0 and asserts that the call is rejected or bounded.
  • Track the adm-zip advisory GHSA-rcw4-f5rp-g42v for a fixed version and pin to it once published. Do not assume 0.6.0 is fixed; the source names no patched release.

5. Indicators of compromise

The source material contains one atomic indicator, a SHA-256 for the 105-byte proof-of-concept ZIP in GHSA-7q85-xj36-vmfc. That advisory is a related resource-exhaustion issue, not the CVE-2026-39244 bypass, and the hash is single-sourced.

type value confidence source
sha256 980d34356fbb248fe527b9d0ac3eabc5c99393a374014be6199523de16709386 medium, single-sourced GHSA-7q85-xj36-vmfc
sha256  980d34356fbb248fe527b9d0ac3eabc5c99393a374014be6199523de16709386

No network indicators, file paths, registry keys or command-line artefacts are present in the source material for the bypass itself.

Behavioural indicators

behaviour where to observe confidence
A ZIP entry whose declared uncompressed size is 0 while the compressed size is non-zero Archive parsing logs, upload handler instrumentation, adm-zip entry metadata high, from the advisory's reproduction steps
A small archive producing a decompressed buffer orders of magnitude larger than the input Process memory metrics, Node heap usage, container OOM events high, from the advisory's impact statement
Process crash or OOM in a service that reads untrusted ZIP files Application error logs, container restart counts, kernel OOM killer logs high, from the advisory's impact statement
A ZIP entry whose declared uncompressed size is implausible relative to the compressed size Archive parsing logs, entry metadata high, from the related advisories

6. Detection

The source material gives usable artefacts for a YARA rule: the distinctive code path in methods/inflater.js, the maxOutputLength option, the expectedLength > 0 condition, and the error string Cannot create a Buffer larger than N bytes. These are artefacts of the vulnerable code and its behaviour rather than of a malicious file, so the rule below targets the vulnerable library build and the error path rather than a payload. It is a hunting aid, not a signature for the bypass itself.

rule ADM_ZIP_INFLATER_MAXOUTPUTLENGTH_CONDITIONAL
{
    meta:
        author = "Adverse Trace"
        date = "2026-09-29"
        reference = "https://github.com/advisories/GHSA-rcw4-f5rp-g42v"
        description = "Detects adm-zip builds where the zlib output cap is applied only when the declared uncompressed size is greater than zero, the condition bypassed by CVE-2026-39244's fix."
    strings:
        $inflater = "methods/inflater.js" ascii
        $maxout = "maxOutputLength" ascii
        $cond = "expectedLength > 0" ascii
        $inflate = "zlib.inflateRawSync" ascii
        $err = "Cannot create a Buffer larger than" ascii
    condition:
        uint32(0) == 0x04034b50 or
        (2 of ($inflater, $maxout, $cond, $inflate)) or
        $err
}

A Sigma rule is not warranted here. The source material describes no process creation, network connection, registry write or scheduled task; the observable is memory consumption inside a Node.js process, which is better covered by the behavioural indicators in §5 and by application-level instrumentation than by a log rule.

CVE assessment

1 referenced CVE

CVE CVSS Exploited EPSS Summary
CVE-2026-39244 7.5 High — 1% adm-zip before 0.5.18 is vulnerable to denial of service via a crafted ZIP file with a manipulated uncompressed size header fie…

7. Sources

  • GitHub Security Advisories (npm), "adm-zip: Decompression-bomb protection (fix for CVE-2026-39244) can be bypassed by declaring uncompressed size as 0", https://github.com/advisories/GHSA-rcw4-f5rp-g42v, 2026-09-29.
  • GitHub Security Advisories (high+), "adm-zip before 0.5.18 is vulnerable to denial of service via a crafted ZIP file", https://github.com/advisories/GHSA-xcpc-8h2w-3j85, date not stated in the source.
  • GitHub Security Advisories (npm), "adm-zip: Uncontrolled memory allocation via the declared uncompressed size (DoS)", https://github.com/advisories/GHSA-7q85-xj36-vmfc, date not stated in the source.

8. Adverse Trace position

Adverse Trace assesses CVE-2026-39244 at CVSS 7.5 HIGH, CWE-400, not in CISA KEV, with EPSS 1%, and the bypass described here does not change that score; it changes the reachability of the flaw, because the cap that was meant to bound it can be switched off by a single header field. The client impact is availability: a service that reads untrusted ZIP files through adm-zip 0.6.0 can be crashed or OOM-killed by a small crafted upload, and the shared code path means every read and extract method is affected. We have no evidence of exploitation in the wild and no fixed version is named in the source, so the interim control is an application-level declared-size and ratio limit in front of adm-zip, not a version upgrade. The bypass detail and the related advisories are single-sourced; verify before enforcement. We will track GHSA-rcw4-f5rp-g42v for a patched release, re-issue this advisory when one is published, and flag any client whose archive-processing endpoints lack an output bound.


Read the original source →

Published via PulseTrace — Adverse Trace threat intelligence.

Post this to LinkedIn
Formatting is converted automatically — headings, bullets, a link back & hashtags. Paste straight in.
J
Jeff Davies