Update dependency adm-zip to ^0.6.0 [SECURITY] #5
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "renovate/npm-adm-zip-vulnerability"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
This PR contains the following updates:
^0.5.17→^0.6.0adm-zip extraction follows destination symlinks, allowing arbitrary file overwrite
CVE-2026-76845 / GHSA-vwc7-r8mq-g2x9
More information
Details
adm-zip 0.5.9 through 0.6.0 follows symbolic links at the extraction destination. Utils.sanitize in util/utils.js enforces containment by comparing only the string form of an archive entry name against the resolved extraction root, and Utils.writeFileTo opens the computed destination with fs.openSync(path, "w", 0o666), which resolves symbolic links and carries neither O_NOFOLLOW nor a pre-write fs.lstatSync check. When a path component at the destination already exists as a symbolic link pointing outside the extraction root, extractAllTo, extractAllToAsync and extractEntryTo write the entry contents through that link and then chmod its target, placing attacker-controlled content in a file outside the root without any traversal sequence appearing in the archive. Reaching the write requires overwrite to be enabled, because the preceding fs.existsSync check also resolves the link and otherwise declines. An attacker able to create a symbolic link inside a shared, reused or predictable extraction directory, such as a temporary directory or a continuous integration workspace, can overwrite any file the extracting process is permitted to write.
Severity
CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
adm-zip: Crafted ZIP file triggers 4GB memory allocation
CVE-2026-39244 / GHSA-xcpc-8h2w-3j85
More information
Details
adm-zip before 0.5.18 is vulnerable to denial of service via a crafted ZIP file with a manipulated uncompressed size header field. In zipEntry.js line 103, Buffer.alloc(_centralHeader.size) allocates memory based on the declared uncompressed size from the ZIP central directory header without validating it against the actual compressed data size or imposing any upper bound. The size value is read directly from the binary header at entryHeader.js line 266 with no bounds check. An attacker can craft a ~120-byte ZIP file that declares ~4GB uncompressed size, causing a memory allocation amplification ratio of over 33 million to 1. The allocation occurs before CRC validation, so the malicious payload cannot be rejected early. All extraction and read methods are affected: readFile(), readAsText(), extractEntryTo(), extractAllTo(), extractAllToAsync(), test(), and entry.getData(). Any application accepting untrusted ZIP files via adm-zip is vulnerable to immediate process crash.
Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:HReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
adm-zip: Uncontrolled memory allocation via the declared uncompressed size (DoS)
CVE-2026-77301 / GHSA-7q85-xj36-vmfc
More information
Details
Summary
adm-zip allocates an entry's output buffer from the declared uncompressed size (central-directory
sizefield) before validating it against the actual data. A tiny crafted ZIP that declares a huge uncompressed size forces a multi-gigabyte allocation from a few bytes.Impact
On adm-zip 0.5.17 (latest), Node 24, a 105-byte ZIP with one stored entry declaring size = 1,774,399,200 makes
new AdmZip(buf).getEntries()[0].getData()commit ~1.8 GB of resident memory in ~4.4 s before throwingError: ADM-ZIP: CRC32 checksum failed, roughly 16 million times the input size. Because the buffer is committed before any validation, on a memory-constrained host (containers, serverless, small VMs) the allocation OOM-kills the process before the CRC check (uncatchable), and concurrent requests can exhaust memory even on larger hosts. Any service that reads entries from untrusted ZIPs is exposed to a remote denial of service.Steps to reproduce
Attachments are not supported in the advisory form, so the 105-byte PoC (sha256
980d34356fbb248fe527b9d0ac3eabc5c99393a374014be6199523de16709386) is inlined as base64 in this self-contained reproducer:The single entry declares uncompressed size = 1,774,399,200 with a compressed size of 5.
getData()allocates the full declared size before the CRC check runs, so the memory is committed regardless of the (tiny) actual payload.Root cause
zipEntry.jsdoesBuffer.alloc(<declared uncompressed size>)before checking the declared size against the compressed size / available bytes.Suggested fix
Validate the declared uncompressed size against the compressed size and a configurable maximum before allocating (yauzl, for example, requires the caller to bound this); reject or stream when the declared size is implausible relative to the input. Happy to send a patch.
Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:HReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
adm-zip: Unhandled error event in async DEFLATE decompression crashes Node.js process (DoS)
GHSA-8238-w5pm-2374
More information
Details
Summary
Denial of Service in
adm-zip's async decompression API allows an unauthenticated attacker to crash the entire Node.js host process by supplying a single malformed ZIP file.Details
Affected package: adm-zip
Affected versions: at least 0.6.0 (current latest); likely all versions containing the current
inflateAsyncimplementation inmethods/inflater.jsPatched version: 0.6.1
Root Cause
methods/inflater.js:12-32(inflateAsync) creates azlib.createInflateRaw(option)stream and feeds it attacker-controlled compressed bytes viatmp.end(inbuf), but never registers an"error"listener on the stream:Per Node.js
EventEmitter/stream semantics, an"error"event emitted with zero listeners is rethrown as an uncaught exception on a later tick, originating from the zlib C++ binding. This cannot be caught by atry/catchwrapped around the calling code, because the throw happens asynchronously, outside the synchronous call stack thetry/catchcovers.This code path is reached from every public async API that decompresses entry data:
readFileAsync,readAsTextAsync,extractAllToAsync, andZipEntry.getDataAsync(zipEntry.js:51,:97-120,:309-315;adm-zip.js:157-164,:192-210,:893).This library recently patched CVE-2026-39244 (GHSA-xcpc-8h2w-3j85), an unbounded
Buffer.alloc()on the synchronous decompression path. That fix added amaxOutputLengthoption tozlib.inflateRawSync/zlib.createInflateRaw, and the sync path's resulting throw is naturally catchable. The async path shares the samemaxOutputLengthoption (methods/inflater.js:5) but has no error-handling on the stream at all, so it was not covered by that fix and remains exploitable via either of two independent triggers:Z_DATA_ERROR) — no special crafting needed.maxOutputLengthguard — but on the stream this surfaces via the unhandled"error"event rather than a catchable throw.Attack Vector
inflateAsyncbegins decompressing; zlib emits"error"onZ_DATA_ERROR.Impact
Any Node.js service that accepts untrusted ZIP uploads and processes them via adm-zip's async API (the documented, recommended pattern for non-blocking servers) can be crashed by a single unauthenticated request containing one small malicious file. This is a full process-level denial of service, not a per-request error.
Proof of Concept
Attached:
poc_async_dos.py. Summary of what it does:new AdmZip(); zip.addFile(...); zip.toBuffer()), guaranteeing correct headers/CRC/offsets.0xFF, corrupting only the DEFLATE payload.new AdmZip(badBuf)(succeeds — headers are intact) and callsentries[0].getDataAsync(callback), wrapped intry/catch, in an isolated child process.Verified independently 3/3 runs (plus 2 isolating controls: an unmodified zip through the same path does not crash; bare Node
zlib.createInflateRaw()fed garbage with no error listener reproduces the identical crash outside adm-zip entirely, confirming the root cause is the missing listener, not something else). Representative output:Reproduction:
python3 poc_async_dos.py(requires python3 and Node.js; tested on Node.js v22.22.1).Suggested Fix
Register an
"error"listener on theInflateRawstream inmethods/inflater.js'sinflateAsync, and route it to the existingcallback, e.g.:The exact callback/error-propagation convention should match the rest of the codebase's async error handling style (a quick look suggests callbacks here are currently success-only; this may need a small signature adjustment or an
err-first convention, at the maintainer's discretion). The key fix is simply: never leave a Node.js stream without an"error"listener when it can plausibly error on attacker-controlled input.Full PoC Source (
poc_async_dos.py)Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:HReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
adm-zip: getDataAsync() bypasses the maxOutputLength size guard enforced by the synchronous getData() path
GHSA-c6fg-446q-cg94
More information
Details
Summary
adm-zip enforces a
maxOutputLengthguard against decompression bombs on its synchronousgetData()path, but the equivalent asynchronousgetDataAsync()path does not enforce it — it accumulates and returns the entire decompressed output regardless of the entry's declared size. An application that checks an entry's declared size before deciding to process it, then reads the entry viagetDataAsync()(a completely ordinary, often-recommended choice for I/O in Node.js), gets none of the protection it believes it has.Details
methods/inflater.js:3-10passes{maxOutputLength: expectedLength}toinflateRawSync, which Node enforces.methods/inflater.js:12-31passes the same option tocreateInflateRawbut never enforces it on the streaming path — it just accumulates every chunk and allocates a final Buffer of whatever size resulted.PoC
Impact
An application that validates a declared size before reading an entry, then uses the async API, gets no protection against a high-ratio DEFLATE payload. Repeated or larger requests could contribute to memory exhaustion.
Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:LReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
adm-zip extraction preserves SUID/SGID bits from untrusted ZIPs -> local privilege escalation
CVE-2026-102282 / GHSA-j5f4-cc29-5x44
More information
Details
Summary
adm-zip applies the Unix permission bits stored in a zip entry directly to the extracted file via
fs.chmodSync()whenkeepOriginalPermission=trueis passed toextractAllTo()/extractEntryTo()— and it never filters the setuid/setgid/sticky bits out of those bits. A zip crafted by an attacker can therefore produce an extracted binary with mode04755. When extraction runs as root (the default posture in Docker builds, CI runners, and privileged install steps — the exact environments where this flag is used), the resulting root-owned setuid file is executed later by a lesser-privileged user, turning the attacker's code into a root execution.Details
The mode a zip entry wants is read back from the external file attributes in the header, and the mask used keeps every special bit:
0xfffis0o7777— it preserves setuid (0o4000), setgid (0o2000) and the sticky bit (0o1000) along with the rwx bits. Shifting by 16 is the standard Unix convention for where zip stores the mode; the mask is the problem.When the flag is on, that value goes straight to the write:
No
& 0o777, no stripping of0o7000. Attacker-controlled bytes in the zip decide the final mode of a file the library creates on disk. Directory entries are affected too (adm-zip.js:855), so a setgid bit on a directory entry also carries over and gives new files inside it group inheritance.PoC
Tested against adm-zip@0.6.0 (latest as of 2026-08-01), Node 22, Linux.
realistic attacker path — no adm-zip APIs involved in creating it):
keepOriginalPermission=false):mode comes out
0666, no setuid. The flag is the enabler.Alternative supply path, if the zip is built in-process with adm-zip's own API:
Impact
Privilege escalation.
The vulnerability class is CWE-732 (incorrect permission assignment): permission bits taken from untrusted input are applied with no filtering.
Realistic chain:
keepOriginalPermission=true. Docker builds run as root by default and CI/install steps commonly do too; this flag is specifically the tooling used in permission-preserving deploy flows.cp -a/rsync mode-bit propagation, into the runtime environment.Who is impacted: applications and pipelines that extract untrusted archives with
keepOriginalPermission=truewhile running as root.Default-usage deployments (flag off) are not affected; non-root extraction results in a harmless self-owned setuid file.
Severity: Medium
Suggested fix, one line in the getter:
Severity
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
adm-zip: Duplicate ZIP entry names: getEntry() and extractAllTo() resolve to different content
GHSA-p634-w6r4-rjp2
More information
Details
Summary
A ZIP file can contain two entries with the identical name. adm-zip keeps both in its internal entry list, but its name-lookup table only retains the last one written.
getEntry(name)andextractAllTo()walk these two different internal structures, so they can each resolve a duplicate name to a different entry. An application that validates a named entry's contents viagetEntry()before trusting an archive, then extracts the whole archive, can end up approving one file's content while a different file's bytes are what actually land on disk under that name.Details
zipFile.js:58-83retains both entries inentryListbut overwritesentryTable[name]with only the last one written.adm-zip.js:83-95,658-663usesentryTableforgetEntry()lookups — returns the last duplicate.adm-zip.js:769-914iteratesentryListfor extraction — writes the first duplicate (sync, default overwrite policy).PoC
Reproduced on the pinned commit (
2b4d84087d45344643e0183756e19191d52815cc)Impact
An application that checks a named entry's content before trusting an untrusted ZIP, then extracts it, can be made to approve different bytes than what actually gets written to disk — the classic check/use split that this kind of validate-then-extract pattern relies on.
Severity
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
adm-zip: Decompression-bomb protection (fix for CVE-2026-39244) can be bypassed by declaring uncompressed size as 0
CVE-2026-92000 / GHSA-rcw4-f5rp-g42v
More information
Details
Affected package: adm-zip (npm)
Affected version: 0.6.0
Summary
The fix shipped for CVE-2026-39244 (
methods/inflater.js) caps zlib's decompression output viamaxOutputLength: expectedLength, whereexpectedLengthis read directly from the ZIP entry's attacker-controlled "uncompressed size" header field (CENLEN/LOCLEN). This cap is only applied whenexpectedLength > 0:If an attacker sets the declared uncompressed-size field to exactly 0, this condition is false,
optionbecomes{}, and no output cap is passed to zlib at all. Node then falls back to zlib's own internal default limit (several GB), so a small, highly-compressible payload can still be decompressed to a very large size in memory -- the same class of resource-exhaustion issue the original CVE addressed, just triggered differently.Steps to Reproduce
0. The compressed bytes and CRC32 are left untouched -- CRC validation still passes because CRC is computed over the real decompressed output, not the declared size.new AdmZip(buffer)and call.getEntries()[0].getData()(orreadFile/readAsText/extractAllTo/etc. -- all share the same code path).Cannot create a Buffer larger than N bytes.Proof of Concept
Attached script demonstrates a controlled A/B comparison using the identical real payload in both cases -- only the declared-size header field differs:
expectedLength > 0.Verified reproducible across 3 independent runs.
Impact
Any application that calls adm-zip's read/extract methods on an untrusted ZIP file (upload handlers, CI artifact extraction, email attachment scanning, etc.) can be made to allocate an amount of memory bounded only by zlib's own internal default rather than any limit the application or adm-zip intends -- a small (tens-of-MB) upload can trigger multi-GB memory consumption, risking process crash/OOM.
Suggested Fix
Apply
maxOutputLengthunconditionally (e.g. defaulting to a sane absolute ceiling, or always passing the declared size regardless of whether it's 0), and/or add an independent compression-ratio check that doesn't rely solely on the attacker-supplied size field.Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:HReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
Configuration
📅 Schedule: (in timezone UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR has been generated by Mend Renovate CLI.
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.