fix(security/medium/): update dependency tar to v7.5.21 [security] - #657
Merged
Conversation
tolzhabayev
approved these changes
Jul 27, 2026
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.20→7.5.21node-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 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
Configuration
📅 Schedule: (in timezone Europe/Berlin)
🚦 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.
Need help?
You can ask for more help in the following Slack channel: #proj-renovate-self-hosted. In that channel you can also find ADR and FAQ docs in the Resources section.