chore(deps): update dependency tar to v7.5.21 [security]#694
Open
renovate[bot] wants to merge 1 commit into
Open
chore(deps): update dependency tar to v7.5.21 [security]#694renovate[bot] wants to merge 1 commit into
renovate[bot] wants to merge 1 commit into
Conversation
renovate
Bot
force-pushed
the
renovate/npm-tar-vulnerability
branch
from
July 21, 2026 03:04
d45078a to
f7cca9b
Compare
renovate
Bot
force-pushed
the
renovate/npm-tar-vulnerability
branch
from
July 25, 2026 00:32
f7cca9b to
6a77987
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
7.5.15→7.5.21node-tar applies PAX size override to intermediary GNU long-name/long-link headers, causing tar parser interpretation differential (file smuggling)
CVE-2026-53655 / GHSA-vmf3-w455-68vh
More information
Details
Summary
tar(node-tar) applies a PAX extended header'ssize=record (and other PAXoverrides) to the next header entry of any type, including intermediary
metadata headers such as a GNU long-name (
L) or long-link (K) entry. PerPOSIX pax, a PAX extended header (
x) describes the next file entry, not theintermediary extension headers that may sit between the
xheader and the fileit annotates. Because node-tar lets the PAX
sizeoverride the byte length ofan intervening
L/K/xheader, an attacker can desynchronize node-tar'sstream cursor relative to every other mainstream tar implementation
(GNU tar, libarchive/bsdtar, Python
tarfile, and the now-fixedtar-rs/astral-tokio-tar).The result is a tar parser interpretation differential (CWE-436): a single
crafted archive yields a different set of members under node-tar than under the
reference tar tools. An attacker can use this to hide a member from one parser
while it is visible to another, which defeats security tooling whose scanner and
extractor disagree on archive contents (e.g. a malware/secret scanner that lists
entries with one library while a downstream step extracts with another). node-tar
is one of the most widely deployed JavaScript tar libraries (it backs
npm's ownpackage-tarball handling and is a transitive dependency of a very large fraction
of the npm ecosystem), so the blast radius for "files that extract differently
depending on the tool" is broad.
This is the same root cause and fix that was just addressed upstream in the Rust
tar ecosystem (
tar-rs/astral-tokio-tar); node-tar carries the equivalentdefect and has no equivalent guard.
Impact
the prior tar "smuggling" advisories GHSA-j5gw-2vrg-8fgx and
GHSA-fp55-jw48-c537).
scans with node-tar and a different member list to GNU tar / libarchive /
Python tarfile (and vice versa). This lets a malicious file be hidden from a
scanner that uses a different parser than the eventual extractor, or hidden
from node-tar-based inspection while still landing on disk via a system
tar.an attacker-supplied tar with node-tar. Tar archives are routinely fetched
from untrusted sources (package registries, user uploads, CI artifacts,
container layers).
RCE; it is a building block for supply-chain / scanner-evasion attacks rather
than a standalone code-execution primitive.
Vulnerable code (file:line)
src/header.ts(compiled todist/esm/header.js:49anddist/commonjs/header.js:85in the publishedtar@7.5.15):exis the currently-accumulated PAX local extended header andgexthePAX global header. The
sizeoverride fromex/gexis appliedunconditionally to whatever header is being decoded next — there is no check
that the header being decoded is a real file entry rather than an intermediary
extension header.
src/parse.ts,[CONSUMEHEADER]constructs the next header with the currentEX/GEXapplied:and later branches on whether that header is a metadata entry.
this[EX]iscleared only in the non-meta (real file) branch:
When the stream is ordered
x (PAX, size=N) -> L (GNU long-name) -> file, theLheader is constructed withthis[EX]still set, so itssize/remainbecomes
Ninstead of theLpayload's true length. node-tar then consumesNbytes of "metadata" and resumes header parsing at the wrong offset, landing
mid-stream. Every other mainstream parser applies the PAX
sizeonly to thefollowing file entry, so they stay synchronized.
The correct behavior (and the fix shipped upstream in the Rust tar ecosystem) is
to not apply PAX
size/overrides when the entry being decoded is itself anextension header (
LGNU long-name,KGNU long-link,xPAX local,gPAXglobal).
How input reaches the sink
tar.list(),tar.extract()/tar.x(), andtar.Parse/tar.Unpackall routeevery 512-byte header block through
Header.decode(...)with thecurrently-accumulated
EX/GEX. Any consumer that parses an attacker-suppliedarchive —
tar.list,tar.extract, or piping into the streamingParser—reaches the sink. No options need to be enabled; the default code path is
affected.
Proof of concept
Archive layout (all standard, GNU-tar-producible blocks):
Generator (
make_tar.py, pure stdlib, no external deps):A negative-control archive is identical except the PAX record is
pax_record('comment', 'x')(nosize=), written topax-control.tar.End-to-end reproduction (against pinned version
tar@7.5.15, latest release)Install the published package into a clean project and parse both archives:
e2e.mjs:Verbatim output:
Reference parsers on the same
pax-desync.tar:Interpretation differential: GNU tar, libarchive (bsdtar), and Python
tarfileall extract the member
longname.txtfrompax-desync.tar, whereas node-tar7.5.15desynchronizes, raisesTAR_ENTRY_INVALID(checksum failure fromlanding mid-stream), and reports zero members. The negative control proves
the divergence is caused solely by the PAX
size=override being applied to theintermediary
Lheader — when the same archive carries a PAX record withoutsize=, node-tar parses it identically to the reference tools(
longname.txt,file_b).Suggested fix
When decoding a header, do not apply PAX
size(or other PAX overrides) if theheader being decoded is itself an extension header. Concretely, in
src/parse.tsclear/ignorethis[EX](andthis[GEX]forsize) when theheader's type is
ExtendedHeader,GlobalExtendedHeader,NextFileHasLongPath(GNU
L), orNextFileHasLongLinkpath(GNUK); equivalently, inHeader.decode, gate theex?.size ?? gex?.sizeoverride on the decoded typenot being one of those extension types. This mirrors the upstream Rust fix,
which guards
pax_sizewithis_gnu_longname || is_gnu_longlink || is_pax_local_extensions || is_pax_global_extensions.A fix PR is being prepared against a private fork and will be linked here.
Fix PR
To be linked from a private fork of the repository (the fix will not be pushed
to any public fork or to upstream during embargo).
Credits
Reported by tonghuaroot.
Severity
CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
node-tar applies PAX size override to intermediary GNU long-name/long-link headers, causing tar parser interpretation differential (file smuggling)
CVE-2026-53655 / GHSA-vmf3-w455-68vh
More information
Details
Summary
tar(node-tar) applies a PAX extended header'ssize=record (and other PAXoverrides) to the next header entry of any type, including intermediary
metadata headers such as a GNU long-name (
L) or long-link (K) entry. PerPOSIX pax, a PAX extended header (
x) describes the next file entry, not theintermediary extension headers that may sit between the
xheader and the fileit annotates. Because node-tar lets the PAX
sizeoverride the byte length ofan intervening
L/K/xheader, an attacker can desynchronize node-tar'sstream cursor relative to every other mainstream tar implementation
(GNU tar, libarchive/bsdtar, Python
tarfile, and the now-fixedtar-rs/astral-tokio-tar).The result is a tar parser interpretation differential (CWE-436): a single
crafted archive yields a different set of members under node-tar than under the
reference tar tools. An attacker can use this to hide a member from one parser
while it is visible to another, which defeats security tooling whose scanner and
extractor disagree on archive contents (e.g. a malware/secret scanner that lists
entries with one library while a downstream step extracts with another). node-tar
is one of the most widely deployed JavaScript tar libraries (it backs
npm's ownpackage-tarball handling and is a transitive dependency of a very large fraction
of the npm ecosystem), so the blast radius for "files that extract differently
depending on the tool" is broad.
This is the same root cause and fix that was just addressed upstream in the Rust
tar ecosystem (
tar-rs/astral-tokio-tar); node-tar carries the equivalentdefect and has no equivalent guard.
Impact
the prior tar "smuggling" advisories GHSA-j5gw-2vrg-8fgx and
GHSA-fp55-jw48-c537).
scans with node-tar and a different member list to GNU tar / libarchive /
Python tarfile (and vice versa). This lets a malicious file be hidden from a
scanner that uses a different parser than the eventual extractor, or hidden
from node-tar-based inspection while still landing on disk via a system
tar.an attacker-supplied tar with node-tar. Tar archives are routinely fetched
from untrusted sources (package registries, user uploads, CI artifacts,
container layers).
RCE; it is a building block for supply-chain / scanner-evasion attacks rather
than a standalone code-execution primitive.
Vulnerable code (file:line)
src/header.ts(compiled todist/esm/header.js:49anddist/commonjs/header.js:85in the publishedtar@7.5.15):exis the currently-accumulated PAX local extended header andgexthePAX global header. The
sizeoverride fromex/gexis appliedunconditionally to whatever header is being decoded next — there is no check
that the header being decoded is a real file entry rather than an intermediary
extension header.
src/parse.ts,[CONSUMEHEADER]constructs the next header with the currentEX/GEXapplied:and later branches on whether that header is a metadata entry.
this[EX]iscleared only in the non-meta (real file) branch:
When the stream is ordered
x (PAX, size=N) -> L (GNU long-name) -> file, theLheader is constructed withthis[EX]still set, so itssize/remainbecomes
Ninstead of theLpayload's true length. node-tar then consumesNbytes of "metadata" and resumes header parsing at the wrong offset, landing
mid-stream. Every other mainstream parser applies the PAX
sizeonly to thefollowing file entry, so they stay synchronized.
The correct behavior (and the fix shipped upstream in the Rust tar ecosystem) is
to not apply PAX
size/overrides when the entry being decoded is itself anextension header (
LGNU long-name,KGNU long-link,xPAX local,gPAXglobal).
How input reaches the sink
tar.list(),tar.extract()/tar.x(), andtar.Parse/tar.Unpackall routeevery 512-byte header block through
Header.decode(...)with thecurrently-accumulated
EX/GEX. Any consumer that parses an attacker-suppliedarchive —
tar.list,tar.extract, or piping into the streamingParser—reaches the sink. No options need to be enabled; the default code path is
affected.
Proof of concept
Archive layout (all standard, GNU-tar-producible blocks):
Generator (
make_tar.py, pure stdlib, no external deps):A negative-control archive is identical except the PAX record is
pax_record('comment', 'x')(nosize=), written topax-control.tar.End-to-end reproduction (against pinned version
tar@7.5.15, latest release)Install the published package into a clean project and parse both archives:
e2e.mjs:Verbatim output:
Reference parsers on the same
pax-desync.tar:Interpretation differential: GNU tar, libarchive (bsdtar), and Python
tarfileall extract the member
longname.txtfrompax-desync.tar, whereas node-tar7.5.15desynchronizes, raisesTAR_ENTRY_INVALID(checksum failure fromlanding mid-stream), and reports zero members. The negative control proves
the divergence is caused solely by the PAX
size=override being applied to theintermediary
Lheader — when the same archive carries a PAX record withoutsize=, node-tar parses it identically to the reference tools(
longname.txt,file_b).Suggested fix
When decoding a header, do not apply PAX
size(or other PAX overrides) if theheader being decoded is itself an extension header. Concretely, in
src/parse.tsclear/ignorethis[EX](andthis[GEX]forsize) when theheader's type is
ExtendedHeader,GlobalExtendedHeader,NextFileHasLongPath(GNU
L), orNextFileHasLongLinkpath(GNUK); equivalently, inHeader.decode, gate theex?.size ?? gex?.sizeoverride on the decoded typenot being one of those extension types. This mirrors the upstream Rust fix,
which guards
pax_sizewithis_gnu_longname || is_gnu_longlink || is_pax_local_extensions || is_pax_global_extensions.A fix PR is being prepared against a private fork and will be linked here.
Fix PR
To be linked from a private fork of the repository (the fix will not be pushed
to any public fork or to upstream during embargo).
Credits
Reported by tonghuaroot.
Severity
CVSS:4.0/AV:L/AC:L/AT:N/PR:N/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).
node-tar: Uncaught Exception DoS via NUL byte in PAX path/linkpath records
CVE-2026-59875 / GHSA-gvwx-54wh-qm9j
More information
Details
Summary
node-tarstrips trailingNULbytes from long-name (L) and long-linkpath (K) GNU extended headers but does not apply the same sanitization to equivalent fields delivered via PAX (xtypeflag) extended headers. A PAX record of the formpath=visible.txt\x00hidden.txtis parsed verbatim intoentry.pathand flows intofs.lstat()/fs.open(), which Node.js core rejects withERR_INVALID_ARG_VALUE. The throw originates inside anFSReqCallbackasync chain that is not wrapped by the consumer'sawait/try-catcharoundtar.x()— it surfaces asuncaughtExceptionand terminates the process.This is a remote denial-of-service primitive against any process that extracts attacker-supplied tarballs through
tar.x/tar.extract/tar.t/tar.Parser, even when the consumer follows the documentedtry/catcherror-handling pattern.A secondary parser-differential (CWE-436) exists because
tar(1),bsdtar, and Pythontarfiletruncate the path at the firstNUL(yieldingvisible.txt) while node-tar retains the full string. A validator that pre-scans a tarball with one tool and extracts with the other is bypassed.Root cause
Vulnerable sink —
src/pax.ts:157-183PAX KV records flow through
parseKVLine. The value half (v) is assigned directly to the result object with no sanitization for embedded NUL bytes:The PAX record body is length-prefixed, so the parser knows the exact byte boundary — but it never checks whether the value half between
=and\ncontainsNUL. The result is consumed byHeader/ReadEntry, whereentry.pathandentry.linkpathcarry the embedded NUL all the way tofs.lstat().Correctly-patched cousin sink —
src/parse.ts:375-388The equivalent code path for GNU L/K long-headers does strip NUL bytes:
The
parse.tsfix is the maintainer's own acknowledgement that path strings on this codepath must be NUL-stripped before reachingfs.*. The PAX path produces the identical primitive but bypasses the guard.Downstream blast radius
entry.pathandentry.linkpathare consumed in:src/unpack.ts→fs.lstat,fs.open,fs.symlink,fs.link,fs.mkdirsrc/list.ts(no crash — listing tolerates NUL in strings)ReadEntryevent that callspath.join()/fs.*onentry.pathThe crash fires inside the FSReqCallback Node-internal async machinery, outside the user's
await tar.x(...)Promise rejection boundary.Proof of Concept
Artifacts
poc-null-byte-crash.tar— 3072 bytes — PAXpath=visible.txt\x00hidden.txtpoc-null-linkpath-crash.tar— 2560 bytes — PAXlinkpath=target\x00garbage(symlink target sink)poc1-pax-prefix.py— minimal PAX-header builder (Python 3, no deps)Tarball generator (minimal repro — Python 3)
Reproduction
Observed output (verified 2026-06-23 against
tar@7.5.16)The exception bypasses the user's
try { await tar.x(...) } catch (e) { ... }block and lands in the globaluncaughtExceptionhandler. In a typical server without that handler, the process exits.Impact
Direct: remote DoS
Any service that ingests attacker-supplied tarballs via node-tar inherits a one-tarball-kills-the-process primitive. Realistic deployments where this is reachable without user interaction:
actions/cache,actions/setup-*extracting toolchains)A correctly-coded consumer that does:
does not catch this throw. The Node process dies and (depending on the supervisor) the worker may take time to respawn or never respawn if it dies during boot.
Secondary: parser-differential validator bypass (CWE-436)
path=visible.txt\x00hidden.txttar -tvf)visible.txt(truncated at NUL)bsdtar -tvfvisible.txt(truncated at NUL)tarfile.list()visible.txt\x00hidden.txt(raw)tar.t({file})tar.x({file})A pre-flight validator using GNU tar or bsdtar will see a benign filename; the subsequent node-tar extraction blows up. This is exploitable against any architecture that lists-and-validates-then-extracts.
Suggested patch
Match the long-name handler in
parse.ts— strip everything from the first NUL onward inparseKVLinevalue parsing:This matches
src/parse.ts:379andsrc/parse.ts:386and closes bothpathandlinkpathsinks in one change.A defense-in-depth follow-up: add an explicit
assert(!v.includes('\0'))(or fail-softreturn set) at the top ofparseKVLineso malformed PAX records that aren't path/linkpath also can't smuggle NUL into other unanticipated consumers (e.g. third-party readers ofentry.header.atimeDate objects constructed fromNumber(v)wherevhad embedded NUL).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).
node-tar: Negative tar entry size causes infinite loop in archive replace
CVE-2026-59874 / GHSA-8x88-c5mf-7j5w
More information
Details
Summary
A checksum-valid tar archive with a negative base-256 encoded entry size can make
tar.replace()loop forever while scanning the existing archive. Applications that update attacker-controlled tar archives can have a worker process pinned indefinitely, causing denial of service.Details
The public
tar.replace()API scans the existing archive before appending replacement entries. During this scan, it parses each tar header and advances the archive position by the parsed entry size rounded to a 512-byte block boundary.Tar supports base-256 encoded numeric fields. A crafted header can encode the entry size as
-512while still carrying a valid checksum. The replace scan accepts that parsed negative size and uses it in the position-advance calculation.For a size of
-512, the computed body skip is-512. The scan then adds the normal 512-byte header step, resulting in no net progress. The scanner repeatedly parses the same header forever and never reaches the append step.This is reachable through the supported package API when the existing archive file is attacker controlled. It does not rely on extraction, dependency behavior, or an uncaught exception.
PoC
Save as
poc.mjsin a project with the vulnerable package installed and run:Impact
An application that calls
tar.replace()on an existing archive supplied or controlled by an attacker can be forced into a non-terminating archive scan. This can consume a worker process indefinitely and cause denial of service. Plain extraction-only workflows are not affected by this finding.Severity
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
node-tar: Process crash via PAX numeric path type confusion
CVE-2026-59871 / GHSA-w8wr-v893-vjvp
More information
Details
Summary
A crafted 2.5KB tar archive crashes any Node.js process that extracts it. The PAX header parser coerces all-digit path values to JavaScript numbers, which causes an uncaught TypeError when downstream code calls
.split('/')on the numeric value. Error handlers andstrict: falsecannot intercept the crash.Details
In
pax.tsline 180,parseKVconverts PAX values matching/^[0-9]+$/to numbers via+v. This applies to all fields includingpathandlinkpath. When a PAX header setspathto an all-digit string like"12345", the value becomes the number12345.This number flows through Header -> ReadEntry -> Unpack.CHECKPATH, where
normalizeWindowsPath(entry.path).split('/')throws a TypeError because numbers don't have.split().The throw is synchronous during event emission and bypasses all error handling:
strict: falsedoes not help'error'event handlers do not catch it'warn'handlers do not catch itDirectory, SymbolicLink, and Link type entries reach CHECKPATH and crash. File type entries crash earlier in Header constructor at
this.path.slice(-1), but that throw is caught and emitted as a warning only.PoC
Create a tar archive with a PAX extended header containing an all-digit path:
Extract it:
The archive is ~2.5KB. The crash is deterministic on every attempt.
Impact
Denial of service. Any application or tool that extracts untrusted tar archives crashes from a single small file. This includes npm (which uses node-tar to extract packages), CI/CD pipelines, file upload processors, and backup tools. The crash cannot be caught by application-level error handling.
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).
node-tar: Decompression/parse DoS via unlimited input
CVE-2026-59873 / GHSA-23hp-3jrh-7fpw
More information
Details
Summary
A Decompression/parse DoS via unlimited input vulnerability in
node-tarallows an attacker to exhaust server resources (disk space and CPU). Because the library does not enforce hard upper bounds on total decompressed data or entry counts, a small, maliciously crafted "Gzip Bomb" can be used to fill a server's storage and crash services.Details
The
node-tarlibrary does not enforce a hard upper bound on archive size or the volume of decompressed data processed during extraction. While themaxReadSizeoption exists, it only controls internal read chunk sizes (default 16MB) and does not limit the total cumulative bytes written to disk.Specifically, in
src/extract.ts, theUnpackstream processes entries as they arrive. There is no total-bytes limit, entry-count limit, or decompression ratio guard. An attacker can provide a TAR header claiming a massive file size (e.g., 10GB) and follow it with highly compressible data (like zeros).node-tarwill continue to extract and write this data until the physical disk is exhausted, as it lacks a mechanism to abort based on global resource consumption.PoC
The following Proof of Concept demonstrates how a tiny compressed input can be expanded into gigabytes of data on the host machine almost instantly.
Observation: You will see the extracted size rapidly climb to 5,000 MB+ within seconds, while the actual data being "sent" through the gzip stream is negligible.
Impact
This is a Denial of Service (DoS) vulnerability. It impacts any application or service that uses
node-tarto extract archives provided by untrusted users (e.g., npm registries, CI/CD pipelines, or file-sharing platforms). An unauthenticated attacker can send a small payload that expands to consume all available disk space, leading to system-wide failure and service outages.Severity
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:HReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
node-tar: Uncontrolled recursion in mapHas/filesFilter allows uncatchable stack-overflow DoS via crafted long-path tar with member selection
GHSA-r292-9mhp-454m
More information
Details
Summary
node-tar(npmtar) contains an uncontrolled-recursion stack-exhaustion DoS in the internalmapHashelper used byfilesFilter. When a consumer callstar.t(...)ortar.x(...)with a non-empty member-selection list, node-tar installs a filter that closes over the recursivemapHas(src/list.ts:33-44).mapHaswalks an entry path upward onepath.dirname()call per recursion with no segment cap. A single crafted tar with a GNU-L(or PAX-x) long-path header can deliver a path of tens of thousands of/-separated segments (up tomaxMetaEntrySize= 1 MiB). The recursion overflows the call stack, throwing an uncatchableRangeErrorthat terminates the Node process on async/streaming consumers.Root Cause
filesFilter(src/list.ts:27-51) is installed whenever a caller passes a member-selection list (src/list.ts:119-122,src/extract.ts:55-57). Its filter is invoked atsrc/parse.ts:253(entry.ignore = entry.ignore || !this.filter(entry.path, entry)) insideParser[CONSUMEHEADER]— and crucially outside the only try/catch in that method (which wrapsnew Headeratsrc/parse.ts:179-183).mapHasrecurses once per path segment with no depth limit. TheUnpackmaxDepthguard (src/unpack.ts:342, in[CHECKPATH]) only runs on the'entry'event, which fires afterCONSUMEHEADERhas already invoked the filter — so the stack overflows before any depth guard executes.tar.t(list) has nomaxDepthat all.Impact
Unauthenticated, remotely-triggerable denial of service: a ~188-byte gzip (≈26 KB tar) crashes any service that lists or extracts selected members from an untrusted archive (package registries, CI artifact/cache restore, upload processors). On async (
await tar.t(...)/tar.x(...)) and streaming/pipeconsumers theRangeErrorescapes the promise as anuncaughtExceptionand terminates the process — standard defensivetry/catcharound the async call does NOT prevent it. (The synchronous API is catchable; the async/stream paths — the dominant server pattern — are not.)Proof of Concept
Empirically reproduced on Node v24.18.0 against built
dist/commonjsof node-tar 7.5.20: 188-byte gzip → 26,112-byte tar (12,000 segments) → uncaughtRangeError: Maximum call stack size exceeded→ process exit. A control run with no member-selection list (filter not installed) parses cleanly (exit 0), isolatingmapHasas the sole cause.Attack Chain
L(or PAXx) long-path header whose body is"a/"×~12000 (~26 KB), followed by a normal file entry.maxMetaEntrySizecaps the meta body at 1 MiB (src/parse.ts:241).tar.t({file},[sel])ortar.x({file,cwd},[sel])(member selection — a documented, common API).Unpack.maxDepth(default 1024) atsrc/unpack.ts:342; decompression-ratio guard.maxDepthlives in[CHECKPATH]on the'entry'event, which fires afterCONSUMEHEADER's filter call — the crash occurs before it (extract exits 1 with default maxDepth).tar.thas no maxDepth. Ratio is ~139× (trivial); no total-bytes cap applies to the uncompressed meta body.this.filter(entry.path)→mapHasrecurses once per/segment (src/list.ts:39).CONSUMEHEADER.new Header(src/parse.ts:179-183); thethis.filter(...)call atsrc/parse.ts:253is outside it. TheRangeErrorpropagates out of the stream write/'data'path → uncaught exception (verified:process.on('uncaughtException')fires; asyncawait+try/catchdoes NOT intercept).Bypass Evidence
mapHasrecursion is member-name-independent: the crash fires even when the requested members do not match the malicious entry path — the attacker only needs the consumer to use member selection.mapHasoverflows at 20k–30k segments; on the real streaming path (atopwrite → CONSUMECHUNK → CONSUMEHEADER → filter) it crashes at ≤8k segments (finder's ~12k estimate is accurate for the reachable path).mapHas.Affected Versions
<= 7.5.20(npmtar).mapHaspresent verbatim on tagv7.5.20(latest GitHub release and npmdist-tag latest); no segment/depth cap insrc/list.tsor theCONSUMEHEADERfilter path; HEAD == 7.5.20, no unreleased fix.Suggested Fix
Rewrite
mapHasiteratively (walkdirnamein awhileloop with a segment/visited cap), or enforce a hard path-segment limit inHeader/Parserindependent ofmaxMetaEntrySize, applied before any per-entry filter runs.Dedup Note
Distinct from CVE-2024-28863 / GHSA-f5x3-32g6-qm9j "lack of folders depth validation" (that bounds
mkdirrecursion during extraction viamaxDepthinUnpack[CHECKPATH]on the'entry'event — a different sink, code path, and fix; runs after the filter and does not apply totar.t). Also distinct from the PAX NUL/numeric-path crash advisories (improper-input-to-fs / type confusion, not recursion) and the gzip-bomb advisory (resource exhaustion on disk writes). None touchlist.ts/filesFilter/mapHasor require member selection.Reported by zx (Jace) — GitHub: @manus-use
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).
Release Notes
isaacs/node-tar (tar)
v7.5.21Compare Source
v7.5.20Compare Source
v7.5.19Compare Source
v7.5.18Compare Source
v7.5.17Compare Source
v7.5.16Compare Source
Configuration
📅 Schedule: (UTC)
🚦 Automerge: Enabled.
♻ Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.