Jan 21, 2026·6 min read·71 visits
Node-tar's locking mechanism failed to account for macOS filesystem quirks where characters like 'ß' and 'ss' are identical. This allowed attackers to bypass path reservations, leading to a race condition where a malicious symlink could be swapped in during file extraction, causing arbitrary file overwrites.
A high-severity race condition in the popular `node-tar` library allows arbitrary file overwrites on macOS systems. The vulnerability stems from a mismatch between how the library normalizes Unicode path names and how the APFS filesystem handles character ligatures (like the German 'ß'), bypassing concurrency safeguards.
In the world of secure software development, the filesystem is not your friend. It is a chaotic, legacy-ridden beast that lies to you about what files exist, where they are, and what they are named. This is especially true when dealing with the unholy trinity of file extraction: symlinks, concurrency, and Unicode.
node-tar is the backbone of the Node.js ecosystem. It’s the engine that unpacks your node_modules every time you run npm install. Because installing dependencies is slow, node-tar tries to be fast. It uses a concurrency model controlled by the jobs option to extract multiple files at once. To prevent the obvious chaos of multiple threads writing to the same file, it implements a system called PathReservations. Ideally, this acts like a bouncer, ensuring that if Thread A is working on /app/config, Thread B has to wait.
But here's the catch: Logic that works perfectly on Linux (ext4) often falls apart on macOS (APFS/HFS+). While Linux is case-sensitive and literal, macOS is "helpful." It treats Hello.txt and hello.txt as the same file. Even worse, it treats certain Unicode ligatures—combined characters like the German Eszett (ß) or the ligature ff—as identical to their expanded ASCII counterparts (ss and ff). CVE-2026-23950 is the story of how that "helpfulness" allowed attackers to walk right past the bouncer.
The root cause of this vulnerability is a classic Time-of-Check Time-of-Use (TOCTOU) race condition, enabled by a logic error in string normalization. The PathReservations system relies on a Map to track which file paths are currently being written to. To check if a path is busy, node-tar generates a cache key from the filename.
Prior to version 7.5.4, the library generated this key by stripping the path of relative dots (..) and applying Unicode Normalization Form D (NFD). The code looked something like this: path.normalize('NFD').toLowerCase(). The developer's assumption was that this would catch all variations of a filename. If you try to extract Foo.txt and foo.txt concurrently, they normalize to the same key, and the second job waits.
However, the NFD normalization form does not decompose the German ß into ss. In the eyes of node-tar, ß-secret.txt and ss-secret.txt were two completely different files. They generated different reservation keys (locks). Consequently, node-tar allowed operations on both files to proceed in parallel.
But the macOS kernel (XNU) disagrees. To APFS, ß and ss are the same inode. By creating a tarball with both names, an attacker could trigger two parallel threads targeting the exact same physical location on the disk, completely bypassing the library's internal locking mechanism.
The fix required a fundamental change in how node-tar views strings. Simply lowercasing a string isn't enough when dealing with aggressive filesystem normalization. The patch introduces a "shake out" technique—forcing the string to expand and contract to catch these edge cases.
Here is the diff that closed the hole. Note specifically the sequence of operations:
// src/normalize-unicode.ts
export const normalizeUnicode = (s: string): string => {
return s
.normalize('NFD')
.toLocaleLowerCase('en')
// The magic fix: round-tripping through UpperCase forces expansion.
// 'ß'.toLocaleUpperCase() becomes 'SS'.
.toLocaleUpperCase('en')
}> [!NOTE]
> Why this works: The .toLocaleUpperCase('en') method is aggressive. It knows that the uppercase version of ß is SS. By converting everything to uppercase after decomposition, the library ensures that both the ligature ß and the ASCII sequence ss resolve to the generic SS. The collision is detected, the lock is shared, and the race condition is neutralized.
To exploit this, we don't need memory corruption; we just need precise timing. The goal is Arbitrary File Overwrite. We want to trick node-tar into writing a file to a sensitive location (e.g., ~/.ssh/authorized_keys) by abusing a symlink.
Here is the attack chain:
Craft the Archive: Create a tarball with two entries.
ß pointing to ~/.ssh/.ss containing the attacker's public key.The Setup: The victim runs tar.extract with jobs: 2 (or higher) on a Mac.
The Race:
node-tar sees ß and ss as different. It grants locks for both immediately.ß -> ~/.ssh/.ss. It checks: "Is ss safe? Does the parent exist?" Since the symlink hasn't been flushed to disk yet (or the check logic ignores the parallel operation), the check passes.The Switch:
./ß (which is also ./ss)../ss for writing.The Impact: Because APFS resolves ./ss to the symlink created by Thread 1, the write operation follows the link. The attacker's public key is written to ~/.ssh/authorized_keys (or the file inside the directory depending on exact path construction). Game over.
While this vulnerability requires specific conditions (macOS, jobs > 1, extracting untrusted tarballs), the context of node-tar makes it critical. This library isn't just a utility; it is the installer for the Node.js universe.
If a package manager or build tool uses a vulnerable version of node-tar to unpack a malicious npm package (a supply chain attack vector), the attacker gains the ability to overwrite any file the user has write access to. In a CI/CD environment, this often means overwriting build scripts, injecting backdoors into the resulting artifacts, or stealing secrets by overwriting configuration files to point to external loggers.
Because the attack relies on filesystem behavior (APFS/HFS+), it is invisible to standard static analysis tools that look for buffer overflows or prototype pollution. It is a logic flaw pure and simple, and those are always the hardest to catch.
The immediate fix is to upgrade node-tar to version 7.5.4. This version includes the improved Unicode normalization logic that correctly identifies ligatures as collisions.
If you cannot upgrade immediately, you must disable concurrent extraction or filter out symlinks. Since the race condition depends on two threads operating simultaneously, setting jobs: 1 effectively mitigates the issue, albeit at the cost of performance.
Alternatively, enforce strict input validation. If your application extracts tarballs from untrusted sources, use the filter option to reject SymbolicLink entries entirely until the patch is applied. Remember: trusting user input is bad, but trusting user input that interacts with the filesystem is suicidal.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:H/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
node-tar isaacs | <= 7.5.3 | 7.5.4 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-367 (TOCTOU Race Condition) |
| Secondary CWE | CWE-176 (Improper Handling of Unicode Encoding) |
| CVSS | 8.8 (High) |
| Attack Vector | Network (Malicious Tarball) |
| Platform | macOS / Node.js |
| EPSS Score | 0.00014 (Low Probability) |
CVE-2022-0492 is a high-severity missing authorization vulnerability in the Linux kernel's Control Groups (cgroups) v1 implementation. The flaw resides within the cgroup_release_agent_write function in kernel/cgroup/cgroup-v1.c, where the kernel fails to validate if the process writing to the release_agent file possesses administrative capabilities in the initial user namespace. This allows a local attacker inside a container with root privileges (UID 0) to abuse user namespaces, mount a cgroups v1 directory, modify the release_agent parameter, and execute arbitrary commands on the host system as host root, effectively achieving a complete container escape.
NocoDB is subject to an insufficient session expiration vulnerability where OAuth access and refresh tokens are not invalidated or revoked during security-sensitive actions such as password changes, forgot-password requests, or password resets. This allows an attacker possessing an active OAuth token to maintain unauthorized persistence.
A vulnerability in the vantage6 federated learning framework allows unauthenticated remote attackers to gain administrative control of the server via hardcoded default credentials (root/root) when deployed under default configurations in versions 4.2.3 and below.
An improper access control vulnerability in the vantage6 node component allows concurrently running algorithm containers to read and modify sensitive input and output files of other tasks. The lack of strict workspace directory isolation exposes a significant attack surface in multi-tenant or federated environments where untrusted algorithms are executed.
TinyMCE versions 6.8.0 through 7.0.1 contain a high-severity Cross-Site Scripting (XSS) vulnerability. The flaw exists in the custom HTML parser and sanitizer module, which incorrectly manages SVG namespace scopes when parsing nested elements. A low-privileged or unauthenticated attacker can submit a crafted HTML payload containing nested SVG structures to bypass sanitization filters, leading to arbitrary JavaScript execution in the context of the victim's browser session.
CVE-2026-47759 is a critical stored Cross-Site Scripting (XSS) vulnerability affecting multiple active branches of the TinyMCE rich text editor. The flaw resides in the editor's handling of user-controlled, prefixed internal attributes, such as data-mce-href, data-mce-src, and data-mce-style. When processing raw HTML inputs, TinyMCE's internal validation schema neglects to inspect these custom prefixed attributes. During HTML serialization, the editor's engine extracts these unsanitized values and copies them back into standard executable attributes, overwriting any previously sanitized standard values and leading to execution of arbitrary code.