Jun 27, 2026·6 min read·56 visits
A validation flaw in pnpm allows malicious packages to register '..' as a binary command name. When the package is uninstalled or updated, pnpm evaluates this command key, resolves it to the parent directory of global binaries (typically PNPM_HOME), and deletes it recursively.
CVE-2026-55699 (also identified as GHSA-4gxm-v5v7-fqc4) is a critical path traversal and arbitrary directory deletion vulnerability in the pnpm package manager. The issue exists because the manifest validation process fails to prevent relative path segments within the package 'bin' keys. When a malicious package containing structured path traversal markers is globally installed and later manipulated, pnpm resolves the target paths through path.join() and passes the resolved paths to a recursive deletion function, resulting in arbitrary directory removal.
The Node.js package manager pnpm supports global binary script generation using the bin field of a package manifest. When globally installing a package, pnpm reads this field, matches the designated key with an execution script, and populates symlinks or binary execution shims within the global binary directory (globalBinDir). This exposes an attack surface where packages exercise control over local filesystem link paths.
The vulnerability is classified under CWE-22 (Improper Limitation of a Pathname to a Restricted Directory) and CWE-73 (External Control of File Name or Path). It affects the validation engine of both the Node.js TypeScript codebase and the Rust-based pacquet command-shim parser. Due to insufficient sanitization, reserved relative path parameters can be stored as valid execution binaries.
Because pnpm uses a local index of installed binaries for maintenance tasks, any malicious command entry is evaluated during subsequent package workflows. If a user uninstalls, overrides, or updates the malicious package, pnpm executes recursive directory removal on the resolved path. This allows unauthenticated, remote attackers to trigger the deletion of arbitrary host directories where the running process has adequate permissions.
The root cause of CVE-2026-55699 is a failure to properly sanitize keys in the bin map of a package's package.json file. The flaw resides in two modules: the TypeScript-based bins/resolver/src/index.ts and the Rust-based pacquet/crates/cmd-shim/src/bin_resolver.rs parity engine.
First, the validation routine verifies command keys using a basic regex validation that checks for URL safety or prefix markers. However, it does not explicitly ban reserved relative directory paths such as "", ".", or "..". This permits structural markers to satisfy the primary regex parser.
Second, the scope-stripping function introduces an evasion vector. When resolving scoped packages (such as @scope/package), pnpm strips the organizational scope. If a manifest defines a binary key like "@scope/..", the stripping function processes the value, removes @scope/, and yields "..". This derived string bypasses validation boundaries because the stripping occurs after or in a manner that isolates it from validation checks.
Finally, when global package management operations (e.g., pnpm global remove) run, pnpm looks up the binary registry. It attempts to map the deletion target using path.join(globalBinDir, binName). If binName is "..", the path resolves to the parent of the binary directory. This resolved string is passed to removeBin() within bins/remover/src/removeBins.ts, which executes recursive directory deletion via fs.rmSync(targetBinPath, { recursive: true, force: true }) without enforcing path confinement.
The vulnerable code path is illustrated by analyzing the resolution logic. Prior to the patch, the resolver evaluated the keys without confirming that the output path remained within the bounds of the global installation directory.
// Vulnerable logic flow (Conceptual TypeScript)
import * as path from 'path';
export function resolveBinTarget(globalBinDir: string, binKey: string): string {
// Bypassed if key is '@scope/..' because stripScope removes '@scope/'
const cleanBinKey = stripScope(binKey);
// Lack of path containment validation
return path.join(globalBinDir, cleanBinKey);
}The corresponding Rust resolution logic in pacquet exhibited a parallel vulnerability where the cmd-shim implementation resolved paths without checking if the binary name resolved to a directory boundary outside of the configured destination root.
// Patched TypeScript resolution logic
import * as path from 'path';
export function resolveBinTarget(globalBinDir: string, binKey: string): string {
const cleanBinKey = stripScope(binKey);
// Strict validation prevents directory traversal names
if (cleanBinKey === '' || cleanBinKey === '.' || cleanBinKey === '..') {
throw new Error(`Invalid binary execution key: ${binKey}`);
}
const resolvedPath = path.resolve(globalBinDir, cleanBinKey);
// Defense-in-depth confinement verification
if (!resolvedPath.startsWith(globalBinDir)) {
throw new Error('Access Denied: Path traversal detected outside of the target binary root');
}
return resolvedPath;
}The fix introduces strict checks inside both the TypeScript module and the Rust Pacquet bin_resolver.rs engine. Keys resolving to relative directory identifiers are rejected, and directory confinement checks block any execution targets that attempt to step outside the designated boundary.
Exploitation relies on an attacker publishing a package with a malformed package.json to a registry reachable by the target user. The attack sequence consists of three phases: publishing, user-initiated global installation, and execution of a management workflow that triggers cleanup.
The attacker crafts a manifest where the bin configuration registers a double-dot or scoped relative key. The following example targets the parent directory of globalBinDir during uninstallation:
{
"name": "@attacker/poc-scope-bypass",
"version": "1.0.0",
"bin": {
"@attacker/..": "./exploit.js"
}
}When the victim installs this package globally using pnpm add -g @attacker/poc-scope-bypass, pnpm registers the package and caches its configuration. At this stage, no deletion occurs. The deletion triggers when the victim runs pnpm remove -g @attacker/poc-scope-bypass or executes an update that replaces the package. pnpm reads the cached manifest, joins globalBinDir with the evaluated ".." string, and calls the uninstaller. The uninstaller invokes recursive deletion on the resolved parent path, erasing the user configuration directory.
The impact of CVE-2026-55699 is classified as High for availability. Because the uninstallation processes rely on recursive file system removal (fs.rmSync with recursive and force parameters active), the execution results in immediate, complete erasure of the resolved target directory.
If the system is running standard configurations, globalBinDir is nested within PNPM_HOME. A value of ".." maps directly to PNPM_HOME, causing the silent deletion of all active global shims, downloaded store metadata, shell configurations, and adjacent global execution tools. This renders all installed global packages unusable and disrupts development configurations.
Because the vulnerability is triggered by a package manager running in the user space, the scope is restricted to directories accessible to the executing user's privileges. No data exposure or privilege escalation has been identified. However, if pnpm is executed in CI/CD pipeline runners or automated build nodes with administrative or system privileges, the path traversal could delete critical system directories.
To resolve this vulnerability, users must update to fixed versions of pnpm. If immediate update cycles are restricted by environment controls, development teams should apply temporary mitigation strategies.
Ensure that all installations are updated according to the product release branch. For the 10.x release line, users must migrate to 10.34.2 or higher. For the 11.x release line, users must migrate to 11.5.3 or higher. These updates contain the patched validation mechanisms in both TypeScript and the Rust package resolver.
Where upgrading is postponed, organizations should enforce package installation controls. Restrict the global installation of packages from untrusted or public registries, and audit the configuration of custom package mirrors. Running static analysis checks on incoming packages can block manifests containing relative path descriptors in binary registration keys.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
pnpm pnpm | < 10.34.2 | 10.34.2 |
pnpm pnpm | >= 11.0.0 < 11.5.3 | 11.5.3 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-22, CWE-73 |
| Attack Vector | Network (Requires User Interaction) |
| CVSS v3.1 Score | 6.5 |
| EPSS Score | 0.00271 |
| Impact | High (Availability - Arbitrary Directory Deletion) |
| Exploit Status | Proof of Concept (PoC) available |
| KEV Status | Not listed |
The software uses external input to construct a pathname that should be within a restricted directory, but it does not properly neutralize special elements such as '..' that can resolve to locations outside of the restricted directory.
CVE-2026-74480 is a critical memory safety vulnerability in the Linux kernel's network bridge multicast routing subsystem (net: bridge) resulting from a Use-After-Free (UAF) condition during fast-leave processing of IGMP/MLD multicast groups.
CVE-2026-21992 is a critical, unauthenticated remote code execution (RCE) vulnerability affecting the REST WebServices component of Oracle Identity Manager (OIM) and the Web Services Security component of Oracle Web Services Manager (OWSM). Exploitation occurs over standard network protocols without user interaction, enabling a complete compromise of target system infrastructure.
An insecure configuration in the diagnostic HTTP server of @rsdoctor/rspack-plugin allowed unauthenticated remote attackers or malicious local websites to retrieve serialized build metadata and full source code modules.
CVE-2026-59980 is a CPU exhaustion vulnerability in python-hyper/hpack, where an unauthenticated remote attacker can trigger an infinite loop or high computational complexity overhead by sending a crafted HTTP/2 stream containing excessive variable-length integer continuation octets.
The PHP email processing library zbateson/mail-mime-parser is vulnerable to multiple algorithmic complexity exploits. By submitting small, highly structured email payloads, remote, unauthenticated attackers can trigger high CPU utilization or out-of-memory states, causing an application-wide denial of service.
CVE-2026-61815 is a high-severity Carriage Return / Line Feed (CRLF) header injection vulnerability in the zbateson/mail-mime-parser library. Due to incomplete sanitization logic, encoded newline sequences within filenames and headers survive parsing and translate into literal CRLF control bytes. When applications process or forward these payloads, the library writes the unescaped control bytes directly into outbound SMTP metadata, allowing remote attackers to inject rogue headers or compromise message integrity.