High severityOSV Advisory· Published Jan 16, 2026· Updated Jan 20, 2026
node-tar Vulnerable to Arbitrary File Overwrite and Symlink Poisoning via Insufficient Path Sanitization
CVE-2026-23745
Description
node-tar is a Tar for Node.js. The node-tar library (<= 7.5.2) fails to sanitize the linkpath of Link (hardlink) and SymbolicLink entries when preservePaths is false (the default secure behavior). This allows malicious archives to bypass the extraction root restriction, leading to Arbitrary File Overwrite via hardlinks and Symlink Poisoning via absolute symlink targets. This vulnerability is fixed in 7.5.3.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
tarnpm | < 7.5.3 | 7.5.3 |
Affected products
1Patches
1340eb285b6d9fix: sanitize absolute linkpaths properly
2 files changed · +94 −26
src/unpack.ts+46 −26 modified@@ -33,6 +33,7 @@ const SYMLINK = Symbol('symlink') const HARDLINK = Symbol('hardlink') const UNSUPPORTED = Symbol('unsupported') const CHECKPATH = Symbol('checkPath') +const STRIPABSOLUTEPATH = Symbol('stripAbsolutePath') const MKDIR = Symbol('mkdir') const ONERROR = Symbol('onError') const PENDING = Symbol('pending') @@ -263,6 +264,46 @@ export class Unpack extends Parser { } } + // return false if we need to skip this file + // return true if the field was successfully sanitized + [STRIPABSOLUTEPATH]( + entry: ReadEntry, + field: 'path' | 'linkpath', + ): boolean { + const path = entry[field] + if (!path || this.preservePaths) return true + + const parts = path.split('/') + if ( + parts.includes('..') || + /* c8 ignore next */ + (isWindows && /^[a-z]:\.\.$/i.test(parts[0] ?? '')) + ) { + this.warn('TAR_ENTRY_ERROR', `${field} contains '..'`, { + entry, + [field]: path, + }) + // not ok! + return false + } + + // strip off the root + const [root, stripped] = stripAbsolutePath(path) + if (root) { + // ok, but triggers warning about stripping root + entry[field] = String(stripped) + this.warn( + 'TAR_ENTRY_INFO', + `stripping ${root} from absolute ${field}`, + { + entry, + [field]: path, + }, + ) + } + return true + } + [CHECKPATH](entry: ReadEntry) { const p = normalizeWindowsPath(entry.path) const parts = p.split('/') @@ -295,32 +336,11 @@ export class Unpack extends Parser { return false } - if (!this.preservePaths) { - if ( - parts.includes('..') || - /* c8 ignore next */ - (isWindows && /^[a-z]:\.\.$/i.test(parts[0] ?? '')) - ) { - this.warn('TAR_ENTRY_ERROR', `path contains '..'`, { - entry, - path: p, - }) - return false - } - - // strip off the root - const [root, stripped] = stripAbsolutePath(p) - if (root) { - entry.path = String(stripped) - this.warn( - 'TAR_ENTRY_INFO', - `stripping ${root} from absolute path`, - { - entry, - path: p, - }, - ) - } + if ( + !this[STRIPABSOLUTEPATH](entry, 'path') || + !this[STRIPABSOLUTEPATH](entry, 'linkpath') + ) { + return false } if (path.isAbsolute(entry.path)) {
test/ghsa-8qq5-rm4j-mr97.ts+48 −0 added@@ -0,0 +1,48 @@ +import { readFileSync, readlinkSync, writeFileSync } from 'fs' +import { resolve } from 'path' +import t from 'tap' +import { Header, x } from 'tar' + +const targetSym = '/some/absolute/path' + +const getExploitTar = () => { + const exploitTar = Buffer.alloc(512 + 512 + 1024) + + new Header({ + path: 'exploit_hard', + type: 'Link', + size: 0, + linkpath: resolve(t.testdirName, 'secret.txt'), + }).encode(exploitTar, 0) + + new Header({ + path: 'exploit_sym', + type: 'SymbolicLink', + size: 0, + linkpath: targetSym, + }).encode(exploitTar, 512) + + return exploitTar +} + +const dir = t.testdir({ + 'secret.txt': 'ORIGINAL DATA', + 'exploit.tar': getExploitTar(), + out_repro: {}, +}) + +const out = resolve(dir, 'out_repro') +const tarFile = resolve(dir, 'exploit.tar') + +t.test('verify that linkpaths get sanitized properly', async t => { + await x({ + cwd: out, + file: tarFile, + preservePaths: false, + }) + + writeFileSync(resolve(out, 'exploit_hard'), 'OVERWRITTEN') + t.equal(readFileSync(resolve(dir, 'secret.txt'), 'utf8'), 'ORIGINAL DATA') + + t.not(readlinkSync(resolve(out, 'exploit_sym')), targetSym) +})
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/advisories/GHSA-8qq5-rm4j-mr97ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-23745ghsaADVISORY
- github.com/isaacs/node-tar/commit/340eb285b6d986e91969a1170d7fe9b0face405eghsax_refsource_MISCWEB
- github.com/isaacs/node-tar/security/advisories/GHSA-8qq5-rm4j-mr97ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.