🛡️ CVE-2026-59875 — tar

🟡 CVSS 5.3 — Medium ✅ No Known Exploit CWE-248 NVD
5.3
CVSS Score
0 Low4 Medium7 High9 Critical10

Description

node-tar: Uncaught Exception DoS via NUL byte in PAX path/linkpath records

Summary

node-tar strips trailing NUL bytes from long-name (L) and long-linkpath (K) GNU extended headers but does not apply the same sanitization to equivalent fields delivered via PAX (x typeflag) extended headers. A PAX record of the form path=visible.txt\x00hidden.txt is parsed verbatim into entry.path and flows into fs.lstat() / fs.open(), which Node.js core rejects with ERR_INVALID_ARG_VALUE. The throw originates inside an FSReqCallback async chain that is not wrapped by the consumer's await/try-catch around tar.x() — it surfaces as uncaughtException and 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 documented try/catch error-handling pattern.

A secondary parser-differential (CWE-436) exists because tar(1), bsdtar, and Python tarfile truncate the path at the first NUL (yielding visible.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-183

PAX KV records flow through parseKVLine. The value half (v) is assigned directly to the result object with no sanitization for embedded NUL bytes:

```ts

// src/pax.ts:157

const parseKVLine = (set: Record<string, unknown>, line: string) => {

const n = parseInt(line, 10)

if (n !== Buffer.byteLength(line) + 1) return set

line = line.slice((n + ' ').length)

const kv = line.split('=')

const r = kv.shift()

if (!r) return set

const k = r.replace(/^SCHILY\.(dev|ino|nlink)/, '$1')

const v = kv.join('=') // <-- NO NUL STRIP

set[k] =

/^([A-Z]+\.)?([mac]|birth|creation)time$/.test(k) ?

new Date(Number(v) * 1000)

: /^[0-9]+$/.test(v) ? +v

: v // <-- v with NULs lands here

return set

}

```

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 \n contains NUL. The result is consumed by Header / ReadEntry, where entry.path and entry.linkpath carry the embedded NUL all the way to fs.lstat().

Correctly-patched cousin sink — src/parse.ts:375-388

The equivalent code path for GNU L/K long-headers does strip NUL bytes:

```ts

// src/parse.ts:375

case 'NextFileHasLongPath':

case 'OldGnuLongPath': {

const ex = this[EX] ?? Object.create(null)

this[EX] = ex

ex.path = this[META].replace(/\0.*/, '') // <-- NUL strip applied

break

}

case 'NextFileHasLongLinkpath': {

const ex = this[EX] || Object.create(null)

this[EX] = ex

ex.linkpath = this[META].replace(/\0.*/, '') // <-- NUL strip applied

break

}

```

The parse.ts fix is the maintainer's own acknowledgement that path strings on this codepath must be NUL-stripped before reaching fs.*. The PAX path produces the identical primitive but bypasses the guard.

Downstream blast radius

entry.path and entry.linkpath are consumed in:

  • src/unpack.tsfs.lstat, fs.open, fs.symlink, fs.link, fs.mkdir
  • src/list.ts (no crash — listing tolerates NUL in strings)
  • Any consumer of the ReadEntry event that calls path.join() / fs.* on entry.path

The 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 — PAX path=visible.txt\x00hidden.txt
  • poc-null-linkpath-crash.tar — 2560 bytes — PAX linkpath=target\x00garbage (symlink target sink)
  • poc1-pax-prefix.py — minimal PAX-header builder (Python 3, no deps)

Tarball generator (minimal repro — Python 3)

```python

#!/usr/bin/env python3

"""Minimal PAX-NUL-injection tarball generator for node-tar PoC."""

import os

def cksum(b):

s = 0

for i, x in enumerate(b):

s += 0x20 if 148 <= i < 156 else x

return s

def pad512(buf):

rem = len(buf) % 512

return buf + b'\0' * (512 - rem) if rem else buf

def hdr(name, size, typeflag, prefix=b'', linkpath=b''):

b = bytearray(512)

b[0:len(name[:100])] = name[:100]

b[100:108] = b'0000644\0'

b[108:116] = b'0001000\0'

b[116:124] = b'0001000\0'

b[124:136] = ('%011o ' % size).encode()

b[136:148] = ('%011o ' % 0).encode()

b[148:156] = b' '

b[156:157] = typeflag

b[157:157+len(linkpath[:100])] = linkpath[:100]

b[257:265] = b'ustar\x0000'

b[265:270] = b'root\0'

b[297:302] = b'root\0'

b[329:337] = b'0000000\0'

b[337:345] = b'0000000\0'

b[345:345+len(prefix[:155])] = prefix[:155]

s = cksum(b)

b[148:156] = ('%06o\0 ' % s).encode()

return bytes(b)

def pax(records)

How this vulnerability can be exploited

This issue can be reached over the network, attack complexity is low, an attacker needs no privileges on the target. No user interaction is required. The scope is unchanged, so the impact stays within the vulnerable component. Rated impact: confidentiality none, integrity none, availability low.

Affected software

CVE-2026-59875 is recorded against 2 packages.

  • tar
  • unknown

Timeline and source

Published on 20 July 2026 and last revised on 21 July 2026. No public exploit is currently recorded for this entry. Record sourced from NVD.

References

github.com (Web)
nvd.nist.gov (Advisory)
github.com (Web)
github.com (Package)
github.com (Web)

Details

Severity Medium
CVSS Score 5.3
CVSS Vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
CWE CWE-248
Public Exploit ✅ No
Source NVD
Published 2026-07-20
Updated 2026-08-12
Modified 2026-07-21
Fix URL N/A

Affected Packages

Software From version Fixed in
tar
unknown

Similar Threats

Vulnerability Monitoring

Track new vulnerabilities in tar

CVE-2026-59875 is rated CVSS 5.3 Medium. BotEraser monitors your WordPress installation and notifies you when software you use appears in our vulnerability database.

Set Up Free Alerts →

No credit card required  ·  Results in minutes

ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.