High severityNVD Advisory· Published Mar 7, 2026· Updated Mar 9, 2026
node-tar: Hardlink Path Traversal via Drive-Relative Linkpath
CVE-2026-29786
Description
node-tar is a full-featured Tar for Node.js. Prior to version 7.5.10, tar can be tricked into creating a hardlink that points outside the extraction directory by using a drive-relative link target such as C:../target.txt, which enables file overwrite outside cwd during normal tar.x() extraction. This issue has been patched in version 7.5.10.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
tarnpm | < 7.5.10 | 7.5.10 |
Affected products
1Patches
17bc755dd85e6parse root off paths before sanitizing .. parts
3 files changed · +20 −10
src/strip-absolute-path.ts+1 −1 modified@@ -8,7 +8,7 @@ const { isAbsolute, parse } = win32 // explicitly if it's the first character. // drive-specific relative paths on Windows get their root stripped off even // though they are not absolute, so `c:../foo` becomes ['c:', '../foo'] -export const stripAbsolutePath = (path: string) => { +export const stripAbsolutePath = (path: string): [string, string] => { let r = '' let parsed = parse(path)
src/unpack.ts+3 −3 modified@@ -277,7 +277,9 @@ export class Unpack extends Parser { const { type } = entry if (!p || this.preservePaths) return true - const parts = p.split('/') + // strip off the root + const [root, stripped] = stripAbsolutePath(p) + const parts = stripped.replace(/\\/g, '/').split('/') if ( parts.includes('..') || @@ -318,8 +320,6 @@ export class Unpack extends Parser { } } - // strip off the root - const [root, stripped] = stripAbsolutePath(p) if (root) { // ok, but triggers warning about stripping root entry[field] = String(stripped)
test/ghsa-8qq5-rm4j-mr97.ts+16 −6 modified@@ -70,6 +70,14 @@ const getExploitTar = () => { }).encode(absWithDotDotHeader, 0) chunks.push(absWithDotDotHeader) + const winAbsWithDotDotHeader = Buffer.alloc(512) + new Header({ + path: 'a/winrootdotslink', + type: 'SymbolicLink', + linkpath: 'c:..\\foo\\bar', + }).encode(winAbsWithDotDotHeader, 0) + chunks.push(winAbsWithDotDotHeader) + chunks.push(Buffer.alloc(1024)) return Buffer.concat(chunks) } @@ -83,9 +91,9 @@ const dir = t.testdir({ const out = resolve(dir, 'out') const tarFile = resolve(dir, 'exploit.tar') -t.test('hardlink escape does not clobber target', async t => { - await x({ cwd: out, file: tarFile }) +t.before(() => x({ cwd: out, file: tarFile })) +t.test('writefile exploits fail', async t => { writeFileSync(resolve(out, 'exploit_hard'), 'OVERWRITTEN') t.equal( readFileSync(resolve(dir, 'secret.txt'), 'utf8'), @@ -100,20 +108,22 @@ t.test('hardlink escape does not clobber target', async t => { }) t.test('symlink escapes are sanitized', async t => { - await x({ cwd: out, file: tarFile }) - t.not(readlinkSync(resolve(out, 'exploit_sym')), targetSym) t.throws(() => lstatSync(resolve(out, 'secret.txt')), { code: 'ENOENT', }) }) t.test('absolute symlink with .. has prefix stripped', async t => { - await x({ cwd: out, file: tarFile }) - t.equal( readlinkSync(resolve(out, 'a/link')), '../a/target', 'symlink target should be normalized', ) + + t.equal( + readlinkSync(resolve(out, 'a/winrootdotslink')), + '..\\foo\\bar', + 'symlink target should be normalized', + ) })
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-qffp-2rhf-9h96ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-29786ghsaADVISORY
- github.com/isaacs/node-tar/commit/7bc755dd85e623c0279e08eb3784909e6d7e4b9fghsax_refsource_MISCWEB
- github.com/isaacs/node-tar/security/advisories/GHSA-qffp-2rhf-9h96ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.