# Race Condition in node-tar Path Reservations via Unicode Ligature Collisions on macOS APFS

node-tar's PathReservations lowercases but doesn't normalize ß/ss collisions on macOS APFS, so parallel extraction of colliding paths bypasses locks, enabling symlink-poisoning arbitrary file overwrite.

- Payout: Research
- Severity: high
- Program: node-tar
- Found by: tomasilluminati, ssushant0011, urielcos
- Disclosed: 2026-01-21
- CVE: CVE-2026-23950
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-r6q2-hw4h-h46w
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-r6q2-hw4h-h46w
- Tags: area:oss domain:oss impact:integrity kind:research platform:ghsa sev:high tech:nodejs technique:race-window technique:unicode-normalization vuln:path-traversal vuln:race-condition

**Target:** npm tar library path-reservations extraction logic
**Root cause:** PathReservations normalizes paths with normalizeUnicode + toLowerCase, but macOS APFS treats ß and ss as the same inode while the reservation key treats them as distinct, so colliding entries are processed in parallel instead of serialized.
**Steps:**
1. On macOS APFS, confirm `printf "CONTENT_SS\n" > collision_test_ss` then `printf "CONTENT_ESSZETT\n" > collision_test_ß` yields only one file (inode collision).
2. Build a tar stream with two entries: `collision_ss` (content A) and `collision_ß` (content B), each 1000 bytes with 512-byte padding, terminated by 1024 zero bytes.
3. Pipe the stream into `new tar.Unpack({cwd: exploitDir, jobs: 8})`.
4. Observe extraction: both entries map to the same APFS inode but reservations didn't serialize them, so one clobbers the other / race window opens for symlink poisoning.
**How found:** Reporter noticed node-tar's reservation key uses normalizeUnicode + toLowerCase, then tested on macOS APFS whether ß and ss actually collide at the filesystem level — they do, revealing a mismatch between the library's key and the FS's inode identity.
**Exploitation:** Attacker crafts a malicious tarball containing colliding filenames (ß vs ss) that resolve to the same APFS inode. Because PathReservations fails to lock them together, node-tar processes them concurrently, allowing a symlink entry to be swapped in between the metadata check and the file write, yielding arbitrary file overwrite outside the extraction directory.
**Escalation:** Chaining the race with a SymbolicLink entry in the archive escalates from a benign collision to arbitrary file write on the victim's filesystem.
**Insight:** When auditing archive extractors, compare the library's path-identity key (normalization + case folding) against the actual target filesystem's inode identity rules — APFS/HFS+ fold ß→ss and NFD-normalize, so any key that only lowercases leaves a parallelization gap.

## Proof of concept (copied exactly from the original report)

**Shell demo of APFS ß/ss collision**

```
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
```

