chore(deps): update pnpm to v10.34.5 [security] #16
Inga granskare
Etiketter
Inga etiketter
Ingen milstolpe
Inget projekt
Inga tilldelade
1 deltagare
Notiser
Förfallodatum
Inget förfallodatum satt.
Beroenden
Inga beroenden satta
Referens
supernaut/next-image-storyblok-loader!16
Läser in…
Hänvisa till i nytt ärende
Ingen beskrivning angiven.
Ta bort grenen "renovate/npm-pnpm-vulnerability"
Borttagning av en gren är permanent. Även om den borttagna grenen kan fortsätta existera en kort tid innan den faktiskt tas bort, kan det INTE ångras i de flesta fall. Vill du fortsätta?
This PR contains the following updates:
10.15.0→10.34.5>=8.0.0→>=10.34.5pnpm v10+ Bypass "Dependency lifecycle scripts execution disabled by default"
CVE-2025-69264 / GHSA-379q-355j-w6rj
More information
Details
pnpm v10+ Git Dependency Script Execution Bypass
Summary
A security bypass vulnerability in pnpm v10+ allows git-hosted dependencies to execute arbitrary code during
pnpm install, circumventing the v10 security feature "Dependency lifecycle scripts execution disabled by default". While pnpm v10 blockspostinstallscripts via theonlyBuiltDependenciesmechanism, git dependencies can still executeprepare,prepublish, andprepackscripts during the fetch phase, enabling remote code execution without user consent or approval.Details
pnpm v10 introduced a security feature to disable dependency lifecycle scripts by default (PR #8897). This is implemented by setting
onlyBuiltDependencies = []when no build policy is configured:File:
pkg-manager/core/src/install/extendInstallOptions.ts(lines 290-291)This creates an allowlist that blocks all packages from running scripts during the BUILD phase in
exec/build-modules/src/index.ts.However, git-hosted dependencies are processed differently. During the FETCH phase, git packages are prepared using
preparePackage():File:
exec/prepare-package/src/index.ts(lines 28-57)The
ignoreScriptsoption defaults tofalseand is completely separate fromonlyBuiltDependencies. TheonlyBuiltDependenciesallowlist is never consulted during the fetch phase.Affected scripts that execute during fetch:
prepareprepublishprepackAttack vectors:
git+https://github.com/attacker/malicious.gitgithub:attacker/maliciousgitlab:attacker/maliciousbitbucket:attacker/maliciousgit+ssh://git@github.com/attacker/malicious.gitgit+file:///path/to/local/repoPoC
Prerequisites:
Steps to reproduce:
Extract the attached poc.zip
Run the PoC script:
Verify the marker file was created by the malicious script:
Manual reproduction:
Create a malicious package with a
preparescript:Initialize it as a git repo and commit the files
Create a victim project that depends on it (just have to make sure it actually git clones and not just downloads a tarball):
Run
pnpm install- the prepare script executes without any warning or approval promptImpact
Severity: High
Who is impacted:
Attack scenarios:
pnpm installWhat an attacker can do:
Why this bypasses security expectations:
onlyBuiltDependenciesconfiguration does not affect git dependenciesSeverity
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 Has Lockfile Integrity Bypass that Allows Remote Dynamic Dependencies
CVE-2025-69263 / GHSA-7vhp-vf5g-r2fw
More information
Details
Summary
HTTP tarball dependencies (and git-hosted tarballs) are stored in the lockfile without integrity hashes. This allows the remote server to serve different content on each install, even when a lockfile is committed.
Details
When a package depends on an HTTP tarball URL, pnpm's tarball resolver returns only the URL without computing an integrity hash:
resolving/tarball-resolver/src/index.ts:The resulting lockfile entry has no integrity to verify:
Since there is no integrity hash, pnpm cannot detect when the server returns different content.
This affects:
"pkg": "https://example.com/pkg.tgz")"pkg": "github:user/repo")"pkg": "git+https://github.com/user/repo")npm registry packages are not affected as they include integrity hashes from the registry metadata.
PoC
See attached pnpm-bypass-integrity-poc.zip
The POC includes:
malicious-packagethat depends on the HTTP tarballvictimproject that depends onmalicious-packageTo run:
The output shows that each install (with
pnpm store prunebetween them) downloads different code despite having a committed lockfile.Impact
An attacker who publishes a package with an HTTP tarball dependency can serve different code to different users or CI/CD environments. This enables:
The attack requires the victim to install a package that has an HTTP/git tarball in its dependency tree. The victim's lockfile provides no protection.
Severity
CVSS:3.1/AV:N/AC:H/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 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:
Audit 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:
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
.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
Given the following package.json:
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: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:to 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:
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:
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 revision values. Validateresolution.commitmatches/^[0-9a-f]{40}$/ibefore passing to Git.Severity
CVSS:3.1/AV:N/AC:H/PR:L/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 Vulnerable to Arbitrary File Write/Delete via Malicious Patch File (Path Traversal)
CVE-2026-50015 / GHSA-rxhj-4m44-96r4
More information
Details
Summary
pnpm's patch application pipeline (
@pnpm/patch-package) performs no path validation on file paths extracted from.patchfiles. An attacker who contributes a malicious patch file via a pull request can write attacker-controlled content to or delete arbitrary files on the filesystem duringpnpm install, as the user running the install. Thediff --githeader paths containing../../sequences traverse out of the package directory, and the traversal is difficult to catch in code review because patch file diff headers are opaque to most reviewers.Vulnerability Details
During
pnpm install, when apatchedDependenciesentry is present inpnpm-workspace.yaml, pnpm reads the referenced.patchfile and applies it via the embedded@pnpm/patch-packagelibrary. TheapplyPatchToDirfunction atpatching/apply-patch/src/index.ts:12-13callsprocess.chdir(opts.patchedDir), setting the working directory to the installed package location deep insidenode_modules/.pnpm/.The patch parser at
@pnpm/patch-package/dist/patch/parse.js:88extracts file paths fromdiff --git a/(.*?) b/(.*?)headers using a regex with no path sanitization. TheexecuteEffectsfunction inapply.jsthen operates on these unsanitized paths:File write (
apply.js:35-49):File delete (
apply.js:13-22):A path like
../../../../../../../../../../home/user/.ssh/authorized_keysin the patch header traverses out of the package directory to an arbitrary location.Proof of Concept
Impact
Arbitrary file write and delete as the user running
pnpm install, limited to paths writable by that user. An attacker who submits a PR adding a.patchfile andpatchedDependenciesconfig can target SSH authorized_keys, shell configuration, CI/CD files, or other writable files. Patch files may receive less review scrutiny thanpackage.jsonchanges because the../traversal sequences are indiff --githeaders that look like patch metadata.Suggested Remediation
Validate parsed patch file paths against the package root directory. Reject any path that resolves outside the patched package directory via
path.resolve+ prefix check. Alternatively, sanitize at parse time by rejecting paths containing..components inparse.js.Severity
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:HReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
pnpm Has an Integrity Check Bypass via Missing Lockfile Integrity Field
CVE-2026-50021 / GHSA-q6j5-fjx5-2mc3
More information
Details
Summary
pnpm's tarball extraction worker skips integrity verification when the
integrityfield is absent from the lockfile resolution. If an attacker can both modifypnpm-lock.yamlto remove theintegrity:field and cause the referenced registry URL to serve altered package content,pnpm install --frozen-lockfilecan install the altered package without an integrity error. npm'snpm cienforces integrity by default; pnpm's behavior of silently skipping verification is a pnpm-specific fail-open gap.Vulnerability Details
The
addTarballToStorefunction inworker/src/start.ts(lines 189-204) checksif (integrity)before verifying the tarball hash. TheTarballResolutiontype declaresintegrityas optional (integrity?: string). When the lockfile omits theintegrityfield, the guard evaluates tofalse, skipping hash verification entirely. The worker then computes a new hash from the unverified content and stores it as legitimate.Proof of Concept
Impact
Supply chain compromise in environments where an attacker can both alter the lockfile and cause the referenced registry URL to serve altered package content. The
--frozen-lockfileflag does not fail closed when the integrity field is missing.Suggested Remediation
Require an
integrityfield for remote tarball resolutions. Change theif (integrity)guard to fail when integrity is absent for non-local packages. When--frozen-lockfileis active, reject lockfile entries that lack integrity for remote packages.Severity
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
pnpm: Repository config can expand victim environment secrets into registry requests before scripts run
CVE-2026-55180 / GHSA-3qhv-2rgh-x77r
More information
Details
Maintainer Action Plan
This report is ready to review with the shared patch branch. Start with the PR and the expected fixed behavior, then use the detailed exploit narrative below only if you want to replay the original path.
CAND-PNPM-122/GHSA-3qhv-2rgh-x77rsecurity/ghsa-batch-2026-06-09a93449314f398cf4bdf2e28d033c02d37395ad22origin/main55a4035abf1ae3fe7208ba1f5ef43c5eff58ccecstart-herepnpm config/env replacement and registry authnpm:pnpm,npm:@​pnpm/config.reader,rust:pacquetCWE-201,CWE-200,CWE-5226.5/CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:NExpected Patched Behavior
Project
.npmrcenvironment placeholders do not expand into registry or auth destinations; the secret is absent from the request URL and auth header.Files And Tests To Review
config/reader/src/loadNpmrcFiles.tsconfig/reader/src/getOptionsFromRootManifest.tsconfig/reader/test/index.tsconfig/reader/test/getOptionsFromRootManifest.test.tspacquet/crates/config/src/npmrc_auth.rspacquet/crates/config/src/npmrc_auth/tests.rspacquet/crates/config/src/workspace_yaml.rspacquet/crates/config/src/workspace_yaml/tests.rs.changeset/sharp-registry-env-placeholders.mdFocused Validation
Run these from a checkout of the shared patch branch. They are the useful maintainer commands with machine-local artifact paths removed.
The full patched replay for the shared branch passed with all 20 candidates marked fixed. This candidate's replay evidence is
results/CAND-PNPM-122-patched-result.json.CAND-PNPM-122: Repository config can expand victim environment secrets into registry requests before scripts run
Advisory Details
Summary
pnpm and pacquet expanded
${ENV_VAR}placeholders from repository-controlled.npmrcandpnpm-workspace.yamlinto registry request destinations and registry credentials. A malicious repository could cause dependency resolution to send victim environment secrets to an attacker-selected registry before lifecycle scripts run.Details
The vulnerable TypeScript pnpm path was:
config/reader/src/loadNpmrcFiles.tsloaded project.npmrcand substituted environment placeholders in keys and values.config/reader/src/getOptionsFromRootManifest.tssubstituted environment placeholders inside workspaceregistry,registries, andnamedRegistriessettings.config/reader/src/index.tsmerged those expanded registry/auth values intopnpmConfig.registries,pnpmConfig.authConfig, andpnpmConfig.configByUri.resolving/npm-resolver/src/fetch.tsbuilt metadata request URLs from the selected registry.network/fetch/src/fetchFromRegistry.tsdispatched the request and attached matching auth headers before install lifecycle scripts could run.The pacquet parity path was:
pacquet/crates/config/src/npmrc_auth.rsexpanded project.npmrcplaceholders while parsing registry URLs and auth values.pacquet/crates/config/src/workspace_yaml.rsexpanded workspace registry placeholders.pacquet/crates/resolving-npm-resolver/src/fetch_full_metadata.rsused the configured registry URL andAuthHeadersfor metadata fetches.PoC
Repository
.npmrcURL-path exfiltration:Repository
.npmrcauth-header exfiltration:Repository
pnpm-workspace.yamlURL-path exfiltration:Exploit method:
CI_JOB_TOKENor another sensitive environment variable present.https://attacker.example/<secret>/<package>orAuthorization: Bearer <secret>to the attacker-controlled endpoint.Validation PoC:
The PoC models the pre-patch URL and Authorization-header leaks, then verifies that patched pnpm and pacquet do not keep the secret in repository-controlled registry destinations or credential values.
Impact
A malicious repository can disclose environment secrets present in a developer or CI process to a repository-selected registry before script controls apply. This can expose npm tokens, CI job tokens, OIDC helper inputs, or other conventional environment secrets if the attacker knows or guesses their names.
Affected Products
Ecosystem: npm
Package name:
pnpm,@pnpm/config.reader; pacquet Rust portAffected versions: current main before this patch, when project
.npmrcorpnpm-workspace.yamlcontains environment placeholders in registry request destinations or project.npmrccontains environment placeholders in registry credential values.Patched versions: pending release containing this patch.
Severity
Severity before patch: High
Vector string before patch:
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:NScore before patch: 7.4
Severity after patch: None
Vector string after patch: not vulnerable after patch
Score after patch: 0.0
Rationale: exploitation is remote and low complexity once a victim runs pnpm or pacquet in the malicious repository. No attacker privileges are required, but user interaction is required. The demonstrated sink is secret disclosure through outbound registry requests, not arbitrary code execution, so confidentiality is high while integrity and availability are not directly impacted by this finding. After the patch, repository-controlled registry destinations and credential values containing env placeholders are ignored, while trusted user/global/auth.ini/CLI config still expands.
Weaknesses
CWE-201: Insertion of Sensitive Information Into Sent Data
CWE-200: Exposure of Sensitive Information to an Unauthorized Actor
CWE-522: Insufficiently Protected Credentials
Patch
The patch makes environment expansion trust-aware for registry requests:
.npmrcno longer expands${...}inregistry,@scope:registry, proxy URL values, URL-scoped keys such as//host/${SECRET}/:_authToken, or registry credential values such as//host/:_authToken=${SECRET}and_authToken=${SECRET}..npmrc, auth.ini, CLI, global, and environment config still support env expansion for trusted registry configuration.pnpm-workspace.yamlno longer expands${...}inregistry,registries, ornamedRegistriesURL values.//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}still expand or lossy-drop as before, preserving setup-node and OIDC trusted-publishing behavior when the.npmrcis supplied as user config.from_project_ini()for project.npmrcand workspace registry filtering.Changed files:
config/reader/src/loadNpmrcFiles.tsconfig/reader/src/getOptionsFromRootManifest.tsconfig/reader/test/index.tsconfig/reader/test/getOptionsFromRootManifest.test.tspacquet/crates/config/src/npmrc_auth.rspacquet/crates/config/src/npmrc_auth/tests.rspacquet/crates/config/src/workspace_yaml.rspacquet/crates/config/src/workspace_yaml/tests.rsChangeset:
.changeset/sharp-registry-env-placeholders.mdPacquet parity:
Ported in the same patch. Pacquet dependency-management commands now parse project
.npmrcwith request-destination and credential-value env expansion disabled, and drop workspace registry values containing${...}placeholders.Verification
Post-patch validation:
The PoC ran:
Results:
cand122-ci-job-tokenin both a request URL and a bearer auth header.config.reader: passed..npmrcdefault registry denial, scoped registry denial, URL-scoped-key denial, project auth-value denial, trusted user.npmrcregistry expansion, trusted user auth-value expansion/lossy fallback, and workspace registry denial.cargo fmt --check: passed..npmrcregistry denial, scoped registry denial, URL-scoped-key denial, auth-value denial, trusted.npmrcregistry expansion, and workspace YAML denial.git diff --check: passed.CVSS Reassessment
The initial scan score used a repository-code-execution vector:
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H(8.8 High)The PoC and source trace showed this finding is direct secret disclosure through registry request URLs or Authorization headers, not a code execution path. The corrected vulnerable vector is:
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:NCorrected vulnerable score: 7.4 High.
Final score after patch: 0.0.
Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
pnpm: Reserved bin name deletes PNPM_HOME during global remove
CVE-2026-55699 / GHSA-4gxm-v5v7-fqc4
More information
Details
Maintainer Action Plan
Maintainer Action Plan
This report is ready to review with the shared patch branch. Start with the PR and the expected fixed behavior, then use the detailed exploit narrative below only if you want to replay the original path.
CAND-PNPM-085/GHSA-4gxm-v5v7-fqc4security/ghsa-batch-2026-06-09a93449314f398cf4bdf2e28d033c02d37395ad22origin/main55a4035abf1ae3fe7208ba1f5ef43c5eff58ccecappendixpnpm global add/remove bin cleanupnpm:pnpmCWE-22,CWE-736.5/CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:HExpected Patched Behavior
Reserved, dot, and path-segment bin names are rejected or ignored; global remove leaves
PNPM_HOMEand the sentinel file intact.Files And Tests To Review
bins/resolver/src/index.tsbins/resolver/test/index.tsglobal/commands/test/globalRemove.test.tspacquet/crates/cmd-shim/src/bin_resolver.rspacquet/crates/cmd-shim/src/bin_resolver/tests.rs.changeset/strange-bin-segments.mdFocused Validation
Run these from a checkout of the shared patch branch. They are the useful maintainer commands with machine-local artifact paths removed.
The full patched replay for the shared branch passed with all 20 candidates marked fixed. This candidate's replay evidence is
results/CAND-PNPM-085-patched-result.json.Title
Reserved manifest bin names can make global package operations delete outside the global bin directory
Description
Summary
Manifest
binobject keys such as"",".", and".."passed pnpm's bin-name guard. When a malicious package was installed globally, later global remove, update, or add-replacement flows could re-derive those names from the installed manifest and passpath.join(globalBinDir, binName)toremoveBin. For"."this targets the global bin directory; for".."this targets its parent.Details
The vulnerable dataflow was:
bins/resolver/src/index.tsconverted manifestbinobject keys tobinNameand only required URL-safe text or$. Empty, dot, dot-dot, and scoped forms such as@scope/..were not rejected after scope stripping.global/packages/src/scanGlobalPackages.tsscanned installed global package manifests and returned manifest-derivedbin.namevalues.global/commands/src/globalRemove.ts,global/commands/src/globalUpdate.ts, and global add replacement logic joined those names toglobalBinDir.bins/remover/src/removeBins.tsrecursively removed the resulting path.Install-time checks did not close the gap: bin target paths were package-root checked, conflict checks looked at the same escaped path but did not reject reserved segments, and bin-link warning paths could leave the package installed for later global operations.
PoC
Run:
The script first performs a safe prepatch simulation in a temporary directory:
It then validates the patched implementation:
The patched resolver no longer emits reserved bin names, and the global-remove regression proves the deletion sink receives only
path.join(globalBinDir, "good").Impact
Direct confidentiality impact was not validated for this primitive; the sink is deletion/corruption, not a read or disclosure path.
Affected Products
Ecosystem: npm
Package name:
pnpmAffected versions: versions before the patch that accept reserved manifest bin names in TypeScript global package flows.
Patched versions: pending release containing the shared bin-name hardening.
Severity
Corrected vulnerable severity: High
Corrected vulnerable vector string:
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:HCorrected vulnerable score: 8.1
Final post-patch score: 0.0, not vulnerable after patch.
The original scan score was 8.3 with
C:H/I:H/A:L. Revalidation removes direct confidentiality impact and raises availability to high because the sink can recursively delete the global bin directory or its parent.Weaknesses
CWE-22: Improper Limitation of a Pathname to a Restricted Directory
CWE-73: External Control of File Name or Path
Patch
bins/resolver/src/index.tsnow rejects empty, dot, and dot-dot bin names after scope stripping.bins/resolver/test/index.tscovers empty, dot, dot-dot, and scoped reserved bin keys.global/commands/test/globalRemove.test.tsproves global remove filters reserved manifest bin names before deletion and only removes a safegoodshim.pacquet/crates/cmd-shim/src/bin_resolver.rsmirrors the same reserved-name rejection; empty names were already rejected.pacquet/crates/cmd-shim/src/bin_resolver/tests.rsextends parity coverage..changeset/strange-bin-segments.mdrecords patch releases for@pnpm/bins.resolver,pnpm, andpacquet.Pacquet parity is appropriate at the shared bin resolver/linker boundary because pacquet dependency-management commands can resolve and link package bins, even though the TypeScript-only global remove/update/add replacement flow is the concrete destructive-delete sink.
Validation
Passed locally:
The script passed TypeScript builds, ESLint,
bins/resolverJest, global-remove sink Jest, pacquet fmt/tests, andgit diff --check.Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:HReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
pnpm: Manifest identity spoof satisfies allowBuilds and runs attacker lifecycle
CVE-2026-55487 / GHSA-5wx6-mg75-v57r
More information
Details
Summary
Keep build approval for opaque dependency sources byte-exact for GHSA-5wx6-mg75-v57r / CAND-PNPM-123.
Merged upstream commit
bf1b731ee6fixed the original name-only approval bypass by making build policy consume the resolved dependency identity. One collision remained: the generic peer-suffix normalizer also stripped parenthesized text from git, URL, tarball, file, and other opaque locators. Approval for one source string could therefore authorize a different attacker-controlled source whose locator normalized to the same value.Security boundary
Exploit replay
allowBuildsapprovingfoo@https://host/pkg.tgz, the upstream implementation also acceptedfoo@https://host/pkg.tgz(evil)because both passed through peer-suffix removal.foo@https://host/pkg@1.0.0(good)andfoo@https://host/pkg@1.0.0(evil)collided because the parser selected the final@and misclassified the opaque URL as a registry package.https://host/pkg@1.0.0could collapsehttps://host/pkg@1.0.0(evil).Files changed
building/policy/src/index.tsandbuilding/policy/test/index.tsnormalize only parsed registry identities and retain exact opaque keys.pacquet/crates/package-manager/src/build_modules.rspasses snapshot identities to policy, matches TypeScript package-separator parsing, and preserves opaque locators.pacquet/crates/package-manager/src/build_modules/tests.rscovers exact approval and denial, all three collision forms, ignored-build output, and registry peer compatibility..changeset/quiet-opaque-build-identities.mdrecords patch releases for@pnpm/building.policyandpnpm.Commands run
Validation
@collision before the additive fix and passed afterward.84bb4b1a046f3a659de1c9aab1d45dcf814124ce.@pnpm/pacquet@0.11.2; no candidate-focused test failed.Patches
10.34.2:github.com/pnpm/pnpm@14bceb1e0b11.5.3:github.com/pnpm/pnpm@bf1b731ee6Compatibility
Registry package approvals keep their existing form. Opaque dependencies that were approved through a normalized parenthesized variant must now use the exact key shown in pnpm's ignored-build output. This is the intended trust-boundary change; no package-resolution or artifact format changes.
CI note
GitHub intentionally does not run status checks on temporary private-fork pull requests. The complete policy suites, formatting, and diff checks above are the applicable validation: https://docs.github.com/code-security/security-advisories/collaborating-in-a-temporary-private-fork-to-resolve-a-security-vulnerability
Written by an agent (Codex, GPT-5).
Severity
CVSS:3.1/AV:N/AC:H/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: Repository-controlled configDependencies can select a pacquet native install engine
CVE-2026-55697 / GHSA-gj8w-mvpf-x27x
More information
Details
Maintainer Action Plan
This report is ready to review with the shared patch branch. Start with the PR and the expected fixed behavior, then use the detailed exploit narrative below only if you want to replay the original path.
CAND-PNPM-097/GHSA-gj8w-mvpf-x27xsecurity/ghsa-batch-2026-06-09a93449314f398cf4bdf2e28d033c02d37395ad22origin/main55a4035abf1ae3fe7208ba1f5ef43c5eff58ccecstart-herepnpm configDependencies / pacquet delegationnpm:pnpm,npm:@​pnpm/config.reader,npm:@​pnpm/installing.commandsCWE-829,CWE-78,CWE-4947.5/CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:HExpected Patched Behavior
config-dependency pacquet install engines are not selected unless the trusted allowlist is set outside the repository; the marker file is not created.
Files And Tests To Review
config/reader/src/Config.tsconfig/reader/src/types.tsconfig/reader/src/configFileKey.tsconfig/reader/src/index.tsconfig/reader/test/index.tsinstalling/commands/src/installDeps.tsinstalling/commands/test/runPacquet.tspnpm/test/install/pacquet.ts.changeset/lucky-config-plugin-pnpmfiles.mdFocused Validation
Run these from a checkout of the shared patch branch. They are the useful maintainer commands with machine-local artifact paths removed.
The full patched replay for the shared branch passed with all 20 candidates marked fixed. This candidate's replay evidence is
results/CAND-PNPM-097-patched-result.json.Summary
pnpm can install
configDependenciesdeclared inpnpm-workspace.yamlbefore command dispatch. Before the patch, a repository could declarepacquetor@pnpm/pacquetas a config dependency and pnpm treated that repository-controlled dependency as an install-engine opt-in. During install, pnpm resolved a platform-specific@pacquet/<platform>-<arch>/pacquetbinary fromnode_modules/.pnpm-config/<packageName>and spawned it as the developer or CI user.Details
The vulnerable source-to-sink path was:
config/reader/src/getOptionsFromRootManifest.tscopies repositorypnpm-workspace.yamlconfigDependenciesinto config.pnpm/src/getConfig.tsinstalls config dependencies before command dispatch.installing/env-installer/src/resolveAndInstallConfigDeps.tsresolves the repository-declared dependency and its optional platform subdependencies.installing/env-installer/src/installConfigDeps.tsfetches, imports, and symlinks the config dependency tree undernode_modules/.pnpm-config.installing/commands/src/installDeps.tsselected pacquet delegation wheneverconfigDependenciescontainedpacquetor@pnpm/pacquet.installing/deps-installer/src/install/index.tscalledopts.runPacquetfrom frozen and materialization paths.installing/commands/src/runPacquet.tsresolved@pacquet/${process.platform}-${process.arch}/pacquetfrom the installed config dependency package and executed it withspawn().Exact-version, integrity, and platform filters only proved which bytes package resolution selected; they did not establish that the repository was trusted to choose a native install engine.
PoC
Standalone PoC and verification script:
Repository fixture:
Registry package shape:
Platform package payload:
Pre-patch exploit model:
pnpm installin the repository..pnpm-config.installDeps()treats the presence ofconfigDependencies.pacquetorconfigDependencies["@​pnpm/pacquet"]as authorization to delegate install materialization.runPacquet()resolves the platform binary from the installed config dependency tree and spawns it in the lockfile directory.Observed PoC output:
Focused validation commands:
Validation result:
getPacquetConfigDependencyName()returnsundefinedwithout a trusted allowlist.getPacquetConfigDependencyName()allows exactpacquet, exact@pnpm/pacquet, and wildcard*trusted opt-in.configDependencyInstallEngineAllowlist, whilepnpm-workspace.yamlcannot grant this permission to itself.@pnpm/config.reader,@pnpm/installing.commands, andpnpm.installing/commands/test/runPacquet.ts: 3 passed.config/reader/test/index.ts: 2 passed, 132 skipped under the focused pattern.config/reader/test/index.tsandpnpm/test/install/pacquet.ts.git diff --check: passed.Impact
A malicious repository can cause pnpm to execute a registry-selected native binary while handling dependency-management commands. The binary runs with the victim developer or CI user's filesystem, environment, registry credentials, git/SSH credentials, and network access.
Affected products
Ecosystem: npm
Package name:
pnpm,@pnpm/config.reader,@pnpm/installing.commandsAffected versions: current main before this patch, when
configDependenciescontainspacquetor@pnpm/pacquetand install paths delegate to pacquet.Patched versions: 10.34.2, 11.5.3.
Severity
Severity: High
Vector string:
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:HBase score: 8.8
Rationale: attacker input is delivered through a repository and registry package, exploitation is low complexity once the victim runs pnpm, no attacker privileges are required, and user interaction is required. Successful exploitation executes a native binary in the victim user's context, with high confidentiality, integrity, and availability impact.
Weaknesses
CWE-829: Inclusion of Functionality from Untrusted Control Sphere
CWE-78: Improper Neutralization of Special Elements used in an OS Command
CWE-494: Download of Code Without Integrity Check
Patch
The patch adds a trusted opt-in gate for config-dependency install-engine delegation:
configDependencyInstallEngineAllowlist.pnpm-workspace.yamlcannot grant this permission to itself; workspace-provided values are discarded after workspace settings are merged.installDeps()delegates to pacquet only whenpacquet,@pnpm/pacquet, or*is present in the trusted allowlist.pacquetas a config dependency, but pnpm will not spawn it as an install engine unless trusted config opts in.Changed files:
config/reader/src/Config.tsconfig/reader/src/types.tsconfig/reader/src/configFileKey.tsconfig/reader/src/index.tsconfig/reader/test/index.tsinstalling/commands/src/installDeps.tsinstalling/commands/test/runPacquet.tspnpm/test/install/pacquet.tsChangeset:
.changeset/lucky-config-plugin-pnpmfiles.mdPacquet parity:
No pacquet-side code-execution sink exists for this finding. The Rust port parses and records
configDependenciesfor workspace-state compatibility, but it does not install config dependencies or select/spawn an alternate install engine from them. The user-visible trust setting is TypeScript-side today because it gates pnpm's pacquet delegation path.CVSS Reassessment
Initial CVSS remains correct for vulnerable versions:
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H/ 8.8 High.Final CVSS after patch: not vulnerable after patch / 0.0. The PoC no longer reaches pacquet install-engine selection or native process execution unless the victim has set a trusted allowlist outside the repository's own workspace settings.
Remaining Risk
Users can explicitly trust pacquet install-engine delegation through the new allowlist. That is intentional behavior; the closed issue is repository self-authorization of a registry-provided native install engine.
Severity
CVSS:3.1/AV:N/AC:H/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: Project env lockfile can short-circuit package-manager resolution and execute lockfile-selected pnpm bytes
CVE-2026-55698 / GHSA-w466-c33r-3gjp
More information
Details
Maintainer Action Plan
This report is ready to review with the shared patch branch. Start with the PR and the expected fixed behavior, then use the detailed exploit narrative below only if you want to replay the original path.
CAND-PNPM-063/GHSA-w466-c33r-3gjpsecurity/ghsa-batch-2026-06-09a93449314f398cf4bdf2e28d033c02d37395ad22origin/main55a4035abf1ae3fe7208ba1f5ef43c5eff58ccecstart-herepnpm packageManager env lockfilenpm:pnpm,npm:@​pnpm/installing.env-installerCWE-829,CWE-494,CWE-3458.8/CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:HExpected Patched Behavior
Committed env-lockfile package-manager entries are force-refreshed through trusted registries before execution; attacker tarball requests and markers stay at zero.
Files And Tests To Review
installing/env-installer/src/resolvePackageManagerIntegrities.tspnpm/src/switchCliVersion.tspnpm/src/switchCliVersion.test.ts.changeset/clean-package-manager-registries.mdFocused Validation
Run these from a checkout of the shared patch branch. They are the useful maintainer commands with machine-local artifact paths removed.
The full patched replay for the shared branch passed with all 20 candidates marked fixed. This candidate's replay evidence is
results/CAND-PNPM-063-patched-result.json.Summary
pnpm can persist package-manager bootstrap metadata in the first YAML document of
pnpm-lock.yaml. Before the patch, direct pnpm execution trusted an already resolvedpackageManagerDependenciesentry when the committed env lockfile contained matchingpnpmand@pnpm/exeversions. A malicious repository could therefore commit package-manager lockfile package records and snapshots that bypassed fresh package-manager resolution, then cause pnpm to install and execute bytes selected by that committed lockfile state during automatic version switching.Details
The vulnerable source-to-sink path was:
lockfile/fs/src/envLockfile.tsreads the repository's first YAML lockfile document and validates shape only.pnpm/src/main.tsreachesswitchCliVersion()when a direct pnpm invocation sees a wantedpnpmpackage manager withonFail=download.pnpm/src/switchCliVersion.tsreads the committed env lockfile when package-manager metadata should be persisted.installing/env-installer/src/resolvePackageManagerIntegrities.tstreatedpackageManagerDependenciesas resolved when only thepnpmand@pnpm/exeversions matched.engine/pm/commands/src/self-updater/installPnpm.tsconverts env-lockfilesnapshotsandpackagesinto the wanted lockfile used byheadlessInstall().pnpm/src/switchCliVersion.tsexecutes the installedpnpmbinary withspawn.sync().The helper fast path is intentionally still version-based for non-execution callers, so the security boundary is enforced at the execution path:
switchCliVersion()now re-resolves already present package-manager env-lockfile entries before they can reachinstallPnpmToStore()andspawn.sync().PoC
Standalone PoC and verification script:
The PoC constructs a committed env-lockfile object with matching package-manager dependency versions and attacker-selected package metadata:
Pre-patch exploit model:
switchCliVersion()and reads the committed env lockfile.pnpm/@pnpm/exeversions short-circuit package-manager resolution.pnpmbinary.Observed primitive proof from the PoC:
The same script then runs the patched
switchCliVersionregression. The regression seeds a poisoned committed env lockfile, has the resolver return a trusted replacement lockfile, and assertsinstallPnpmToStore()receives the trusted lockfile rather than the committed one. This would fail on the vulnerable control flow because the resolver was not called and the committed lockfile reached the installer.Focused validation commands:
Validation result:
switchCliVersion()callsresolvePackageManagerIntegrities()withforce: truewhen committed env-lockfile package-manager entries already satisfy the requested version.switchCliVersion()assigns the resolver return value back toenvLockfile.@pnpm/installing.env-installerandpnpm.switchCliVersion.test.ts.git diff --checkpassed.Impact
A malicious repository can cause arbitrary package-manager code execution in the victim's developer or CI environment before normal command handling continues. That code executes with the victim user's privileges and can read local secrets, alter project files, mutate dependency state, or run further commands.
Affected products
Ecosystem: npm
Package name:
pnpm,@pnpm/installing.env-installerAffected versions: current main before this patch; direct pnpm execution with package-manager auto-switching and a repository-controlled env lockfile.
Patched versions: pending release containing this patch.
Severity
Severity: High
Vector string:
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:HBase score: 8.8
Rationale: the malicious source is repository-controlled package-manager lockfile state delivered through normal supply-chain channels. Exploitation is low complexity once the victim runs pnpm directly, no attacker privileges are required, and user interaction is required. Successful exploitation executes attacker-selected package-manager code in the victim user's security context, with high confidentiality, integrity, and availability impact.
Weaknesses
CWE-829: Inclusion of Functionality from Untrusted Control Sphere
CWE-494: Download of Code Without Integrity Check
CWE-345: Insufficient Verification of Data Authenticity
Patch
The patch makes automatic package-manager switching re-resolve repository-provided bootstrap metadata before install and execution:
resolvePackageManagerIntegrities()acceptsforce, which bypasses the version-only fast path.switchCliVersion()creates a store controller even when the committed env lockfile already contains satisfying package-manager dependency versions.switchCliVersion()callsresolvePackageManagerIntegrities()withforce: truefor already resolved package-manager entries.switchCliVersion()assigns the returned env lockfile back toenvLockfile, soinstallPnpmToStore()installs from freshly resolved metadata.Changed files:
installing/env-installer/src/resolvePackageManagerIntegrities.tspnpm/src/switchCliVersion.tspnpm/src/switchCliVersion.test.tsChangeset:
.changeset/clean-package-manager-registries.mdPacquet parity:
No pacquet-side patch is required for this finding because pacquet does not implement pnpm's package-manager auto-switch path or
installPnpmToStore().CVSS Reassessment
Initial CVSS remains correct for vulnerable versions:
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H/ 8.8 High.Final CVSS after patch: not vulnerable after patch / 0.0. The PoC still demonstrates the underlying unforced env-lockfile reuse primitive, but the patched execution path force-refreshes package-manager metadata through trusted bootstrap registries before install or execution.
Remaining Risk
The helper
resolvePackageManagerIntegrities()still has an unforced fast path that treats matchingpnpmand@pnpm/exeversions as resolved. Current execution-sensitive callers either use trusted roots/registries or pass through the patchedswitchCliVersion()boundary, but future execution paths should useforce: truebefore installing or executing package-manager bytes from repository-provided env-lockfile metadata.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:
patch-removecould delete project-selected files outside the patches directoryCVE-2026-59194 / GHSA-72r4-9c5j-mj57
More information
Details
Summary
The
patch-removedeletion-scope issue tracked as GHSA-72r4-9c5j-mj57 / CAND-PNPM-030 has been addressed in pnpm.A crafted patch entry could resolve outside the configured patches directory and cause
pnpm patch-removeto delete an arbitrary reachable file. This patch validates the configured directory and every resolved target before unlinking anything, then deletes the final directory entry without following it.Security boundary
..while still rejecting parent traversal, Windows drive escapes, and UNC escapes.Exploit replay
Before the patch, a workspace
patchedDependenciespath that resolved outside the project causedpnpm patch-removeto delete the external sentinel. A second replay used a nested parent symlink and a dangling outside victim:realpath()returnedENOENT, yet the victim was still removed. With this patch, both paths are rejected and the outside entries remain intact.Files changed
patching/commands/src/isSubdirectory.tsperforms component-aware containment checks.patching/commands/src/patchRemove.tsvalidates the full batch, canonicalizes parents, and unlinks final entries without following them.patching/commands/test/{isSubdirectory,patchRemove}.test.tscovers traversal, nested symlinks, dangling victims, and valid removals.Commands run
Validation
git diff --check: passed./private/tmp.Patches
10.34.4:github.com/pnpm/pnpm@352ae489f111.7.0:github.com/pnpm/pnpm@612a2e6a73Compatibility
Missing patch files remain no-ops. Valid symlinked patch directories continue to work when their canonical target stays inside the lockfile directory, and final symlinks are removed without touching their targets.
patch-removeis not yet in pacquet's command surface, so no Rust-side parity change is required.Remaining risk
Portable Node APIs do not expose directory-fd-relative
unlinkat(). A local attacker who can replace an already validated parent directory before the unlink may still win a time-of-check/time-of-use race. The reproduced repository-controlled traversal and symlink paths do not require that concurrent capability and are blocked by this patch.Written by an agent (Codex, GPT-5).
Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:LReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
pnpm: Hoisted install imports lockfile alias outside node_modules
CVE-2026-59196 / GHSA-fr4h-3cph-29xv
More information
Details
Summary
The hoisted dependency alias issue tracked as GHSA-fr4h-3cph-29xv / CAND-PNPM-059 has been addressed in both pnpm and pacquet.
A crafted lockfile alias could be joined directly under a hoisted
node_modulesdirectory. Traversal aliases could escape that directory, while reserved aliases such as.binor.pnpmcould overwrite pnpm-owned layout. This patch validates package-name semantics and path containment before graph insertion or filesystem work.Security boundary
dep.namesink.dep.0.namebefore adding the graph node or recursing.ERR_PNPM_INVALID_DEPENDENCY_NAME.Exploit replay
Before the patch, a traversal alias in a hoisted lockfile imported package files outside the intended install root. With this patch, both pnpm and pacquet reject the alias before graph insertion or filesystem work, and the escaped file is not created.
Files changed
fs/symlink-dependency/src/safeJoinModulesDir.tsprovides the TypeScript containment helper.installing/deps-restorer/src/lockfileToHoistedDepGraph.tsvalidates the parsed dependency name at the hoisted graph sink.pacquet/crates/package-manager/src/{hoisted_dep_graph.rs,safe_join_modules_dir.rs}mirrors that boundary in Rust.Commands run
Validation
cargo fmt, parsed two-document lockfile validation, andgit diff --check: passed.Patch
Ready-for-review private PR: https://github.com/pnpm/pnpm-ghsa-fr4h-3cph-29xv/pull/1
GitHub reports the branch as mergeable and has requested review from
zkochan. GitHub intentionally does not run status checks on temporary private-fork PRs; the commands and outcomes above are the recorded local validation: https://docs.github.com/code-security/security-advisories/collaborating-in-a-temporary-private-fork-to-resolve-a-security-vulnerabilityCompatibility
Valid unscoped and scoped package aliases continue to work. The changeset covers
@pnpm/fs.symlink-dependency,@pnpm/installing.deps-restorer, andpnpm; pacquet is updated in the same commit for CLI parity.Written by an agent (Codex, GPT-5).
Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:LReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
pnpm: Path traversal in configDependencies env lockfile allows symlink creation outside node_modules/.pnpm-config
CVE-2026-59195 / GHSA-qrv3-253h-g69c
More information
Details
Summary
pnpmaccepts package names from the env lockfileconfigDependenciessection and uses those names directly when creating config dependency symlinks undernode_modules/.pnpm-config.A malicious repository can commit a crafted
pnpm-lock.yamlwhose env-lockfile document contains a traversal-shaped config dependency name such as../../PWNED_CFGDEP. Duringpnpm install, pnpm installs the config dependency and creates a symlink at a path derived from that name.In local testing against pnpm
v11.5.1, this caused pnpm to create a symlink outside the intended config dependency directory:This works with
--ignore-scripts, so it does not rely on lifecycle script execution.Vulnerable behavior
The vulnerable behavior appears to be that
configDependencieskeys from the env lockfile are trusted as package names and used in filesystem paths without rejecting traversal components.The relevant pattern is:
If
pkgNameis attacker-controlled and contains.., thenpath.join(configModulesDir, pkgName)can resolve outsidenode_modules/.pnpm-config.Impact
A malicious project can cause pnpm to create symlinks outside the intended
node_modules/.pnpm-configdirectory during install.This gives an attacker a filesystem write primitive in the victim project directory, and potentially outside it with deeper traversal payloads, depending on path permissions and platform behavior.
The issue is especially relevant because:
pnpm-lock.yaml.pnpm install.--ignore-scripts.Local proof of concept
The following local-only PoC creates a temporary project, starts a local fake registry on
127.0.0.1, writes a malicious env-lockfile entry, runs pnpm, and checks whether pnpm created a symlink outsidenode_modules/.pnpm-config.Command used:
Observed output:
pnpm output:
The PoC then detected the escaped symlink:
Malicious lockfile structure
The malicious input is an env-lockfile
configDependencieskey containing traversal components:pnpm accepts the traversal-shaped name and reports it as installed:
Security boundary violation
The intended config dependency root was:
But pnpm created:
This demonstrates that a config dependency name from the lockfile can escape the directory where config dependencies should be linked.
Suggested remediation
Validate every
configDependencieskey loaded from the env lockfile before using it as a package name or path component.Recommended fixes:
Reject env-lockfile
configDependenciesnames that are not valid npm package names.Reject names containing absolute paths,
.components,..components, backslashes, or platform-specific path separators.Use containment-checked path joining before creating symlinks:
node_modules/.pnpm-config,Apply the same validation to config dependency subdependencies and optional dependency names read from the env lockfile.
Intersect env-lockfile
configDependencieswith the effectivepnpm-workspace.yamlconfigDependenciesbefore installing, so extra lockfile-only entries are rejected.A safe destination check should enforce behavior equivalent to:
Name validation should happen before this check, not instead of it.
Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:H/A:LReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
pnpm: Virtual store linker path traversal via unvalidated depPath name in lockfileToDepGraph
CVE-2026-82392 / GHSA-c59q-g84q-2gj5
More information
Details
Summary
The virtual store linker constructs package installation directories using
path.join(modules, pkgName)wherepkgNameis extracted from lockfilepackageskeys viadp.parse(depPath).namewithout validation. A craftedpnpm-lock.yamlwith traversal sequences in depPath keys (e.g.,../../../tmp/pwned@1.0.0) causes package content to be written to arbitrary filesystem paths duringpnpm install.This is an incomplete fix of GHSA-fr4h-3cph-29xv — the
safeJoinModulesDircontainment helper was applied to the hoisted linker andsymlinkDependencybut NOT to the virtual store linker'slockfileToDepGraph.ts:233.Details
Root Cause
dp.parse()atpnpm11/deps/path/src/index.ts:135extracts the package name as:This is a raw substring operation with zero validation that
nameis a valid npm package name. A depPath of../../../tmp/pwned@1.0.0yieldsname = '../../../tmp/pwned'.Vulnerable Code Path
pnpm-lock.yaml→lockfile.packages['../../../../../../../tmp/pwned@1.0.0'](attacker-controlled lockfile key)nameVerFromPkgSnapshot(depPath, pkgSnapshot)atlockfile/utils/src/nameVerFromPkgSnapshot.ts:16→ callsdp.parse(depPath)→ returns{ name: '../../../../../../../tmp/pwned' }lockfileToDepGraph.ts:232→modules = path.join(dirInVirtualStore, 'node_modules')lockfileToDepGraph.ts:233→dir = path.join(modules, pkgName)→ resolves to/tmp/pwned(ESCAPES virtual store)storeController.importPackage(depNode.dir, ...)→ writes package content to the traversed pathWhy Existing Defenses Don't Catch It
depPathToFilename()— replaces/with+for thedirInVirtualStorepath, butpkgNamecomes SEPARATELY fromdp.parse()and is NOT passed through this functionverifyLockfileResolutions()— validates dependency map keys (aliases) viaisValidDependencyAlias(), but never validates the depPath keys themselvesyaml.load(lockfileRawContent)with no schema validation onpackageskeysimportPackage()— acceptstargetDirand passes it directly tocafsStore.importPackage(targetDir, ...)with zero containment checkEscalation to RCE (non-default config)
When
dangerouslyAllowAllBuilds: trueis configured (or the traversal package name is in the explicitallowBuildslist), the same traversed path is used in the rebuild phase atafter-install/src/index.ts:402,470. The attacker'spostinstallscript then executes with the victim's shell access. Under default config,allowBuildreturns false for unknown packages, limiting impact to arbitrary file write.Also Affected (PnP linker)
When
nodeLinker: pnpis configured,lockfileToPackageRegistry()atlockfile/to-pnp/src/index.ts:105-110uses the same unvalidateddp.parse().nameinpackageLocationconstruction, allowing the.pnp.cjsresolver map to point outside the virtual store. This is a lower-impact variant (PnP is not the default linker).Impact
An attacker who can commit a crafted
pnpm-lock.yamlto a repository (or supply one via a malicious package) can cause arbitrary file writes on the machine of any user who runspnpm install. Written content is the actual package files from a real npm package (attacker controls which package and which destination).Targets for arbitrary file write include:
.git/hooks/pre-commit— code execution on next git operation~/.local/bin/— binary hijackingReproduction
Craft a
pnpm-lock.yaml:Run
pnpm install— package content is written to/tmp/pwned/instead of the virtual store.Recommended Fix
Apply
safeJoinModulesDir(or equivalent validation) at:lockfileToDepGraph.ts:233—path.join(modules, pkgName)after-install/src/index.ts:402—path.join(pkgModulesDir(depPath), pkgInfo.name)lockfile/to-pnp/src/index.ts:105-110— PnPpackageLocationAlternatively, validate depPath keys during lockfile parsing to reject any that don't produce valid npm package names via
dp.parse().Relationship to GHSA-fr4h-3cph-29xv
GHSA-fr4h-3cph-29xv fixed the hoisted linker path (
lockfileToHoistedDepGraph.ts:222) by addingsafeJoinModulesDir. The same fix was NOT applied to the virtual store linker, which uses the identicaldp.parse().name → path.join()pattern atlockfileToDepGraph.ts:233.Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:LReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
pnpm: A tarball dependency's manifest
nameescapes node_modules → arbitrary file write/overwrite on installCVE-2026-82393 / GHSA-vq4v-j7r6-jq4m
More information
Details
Summary
When resolving a package, pnpm uses the resolved manifest
nameas a raw path segment for the isolated-linker import target. A tarball dependency whosepackage.jsonnameis a scoped path traversal (@x/../../…/<abs path>) is therefore extracted outsidenode_modules, to an attacker-chosen absolute path, and can overwrite existing files there. Attacker controls the destination, filenames, and contents → arbitrary file write → code execution (e.g.~/.zshrc,.git/hooks/pre-commit, another package's code). Occurs duringpnpm installeven with--ignore-scripts(no lifecycle scripts run), defeating that safety.Same class as the just-patched GHSA-hwx4 (transitive-dependency alias traversal) and GHSA-v23m (
stage downloadmanifest name/version traversal), in a sink their fixes did not cover: the isolated-linker import target keyed by the resolved name.Root cause
path.join(modules, <resolved name>)ininstalling/deps-resolver/src/resolvePeers.ts:706,installing/deps-resolver/src/index.ts:614, anddeps/graph-builder/src/lockfileToDepGraph.ts:233— without thesafeJoinModulesDirguard used on the symlink/hoisted/bin paths (installing/deps-restorer/src/lockfileToHoistedDepGraph.ts:222). The store location isnode_modules/.pnpm/<id>/node_modules/<name>, so a traversal<name>escapes.resolving/npm-resolver/src/pickPackage.ts:753) rejects only unscoped names containing/, so a scoped@x/../..passes.Steps to reproduce
Self-contained PoC (real
pnpm@11.9.0; loopback tarball server; escape target is a throwaway temp dir):Confirmed output (
repro/poc.mjs, exit 0):Remediation
Route the isolated-linker import-target joins (
resolvePeers.ts:706,deps-resolver/index.ts:614,lockfileToDepGraph.ts:233) throughsafeJoinModulesDir(as the hoisted linker already does), and/or enforcevalidate-npm-package-nameon the resolved manifest name (close the scoped-name gap atpickPackage.ts:753) so the import target rejects a traversal name and re-asserts containment before any write.Severity
CVSS:3.1/AV:N/AC:H/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: Environment secrets exfiltrated via env-placeholder expansion in proxy settings read from an untrusted pnpm-workspace.yaml
CVE-2026-101043 / GHSA-vx52-2968-3vc6
More information
Details
Summary
pnpm expands
${VAR}environment placeholders in thehttpProxy/httpsProxy/noProxysettings read from a project'spnpm-workspace.yaml. Because a project manifest is repository-controlled, a malicious repository that a victim merely clones and runspnpm installin can route all install traffic through an attacker proxy whose hostname or userinfo embeds — and thereby exfiltrates — an environment secret such asNPM_TOKENorGITHUB_TOKEN.This bypasses a trust boundary pnpm deliberately enforces: env-placeholder expansion of request-destination settings is already suppressed for
registry,pnprServer,registriesandnamedRegistrieswhen they come from an untrusted project manifest, and the sibling.npmrcreader already classifies the proxy keys as request destinations. The manifest-side guard set simply omitted them.Impact
An attacker who controls only the contents of a repository's
pnpm-workspace.yaml— a public repo, a fork, or a supply-chain pull request — can read many values out of the victim's process environment and have them delivered to an attacker-controlled host. No pre-existing access to the victim's store, global config, lockfile,node_modules, or environment is required. The secret is exfiltrated during config loading, before any lifecycle script runs.This turns "I can author a project manifest" into "I read the victim's environment secrets."
Affected versions
Introduced in pnpm 10.7.0, which added environment-variable expansion in setting names and values.
>= 11.0.0, < 11.11.0>= 10.7.0, < 10.34.5The Rust port (
pacquet) and the registry server (pnpr) are not affected.Patches
The fix adds
httpProxy,httpsProxy,noProxy,proxyandnoproxyto the request-destination key set in@pnpm/config.reader(src/getOptionsFromRootManifest.ts), so env placeholders in proxy settings from an untrusted manifest are dropped rather than expanded — matching the existingregistry/pnprServerhandling and the.npmrcreader'sisRequestDestinationValueKey. Regression tests cover the proxy keys.Workarounds
Upgrade to a patched version. Until then, do not run pnpm commands in an untrusted repository in an environment that holds secrets, or inspect the repository's
pnpm-workspace.yamlfor proxy settings before installing.Proof of concept
With
NPM_TOKENset in the victim's environment,pnpm installexpands the placeholder and routes install traffic through the attacker's host, whose hostname (and DNS query) carries the token.Unit level:
Using
registryorpnprServerin place ofhttpsProxydoes not leak on either version — those keys were already guarded, which is what made the proxy keys a hole in an existing boundary rather than an unguarded surface.Credit
Reported privately. A second finding in the original report — the
Authorizationheader being retained across a same-hosthttps->httpredirect — was assessed and is not treated as a pnpm vulnerability: npm (make-fetch-happen,minipass-fetch), Yarn (got) and reqwest all compare host rather than origin, and a registry that redirects from HTTPS to plaintext HTTP is itself the broken component. That behavior is being discussed publicly at https://github.com/orgs/pnpm/discussions/13598.Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
pnpm incorrectly parses tar archives relative to specification
CVE-2023-37478 / GHSA-5r98-f33j-g8h7
More information
Details
Summary
It is possible to construct a tarball that, when installed via npm or parsed by the registry is safe, but when installed via pnpm is malicious, due to how pnpm parses tar archives.
Details
The TAR format is an append-only archive format, and as such, the specification for how to update a file is to add a new record to the end with the updated version of the file. This means that it is completely valid for an archive to contain multiple copies of, say,
package.json, and the expected behavior when extracting is that all versions other than the last get ignored.This is further complicated by that during tarball extraction, all package managers are configured to drop the first path component, so collisions can be created simply by using multiple root folders in the archive, even without performing updates.
When pnpm extracts a tar archive via tar-stream, it appears to extract only the first file of a given name and discards all subsequent files with the same name.
PoC
Create a root folder with the following layout:
a/package.jsonpackage/package.jsonz/package.jsonFile contents:
a/package.json
package/package.json
z/package.json
Then use the tar binary to produce a tarball (working directory is the root folder):
tar -c -z --format ustar -f package.tgz a package zThe order of the folders at the end matters; whichever one is last will end up being the package.json that wins when extracted by npm; the one that is first will be the one that wins when extracted by pnpm.
Install the tarball via the
file:protocol.Observe that with npm, the lockfile has
react@17, while with pnpm it hasreact@15.Impact
This can result in a package that appears safe on the npm registry or when installed via npm being replaced with a compromised or malicious version when installed via pnpm.
Severity
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:HReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
pnpm no-script global cache poisoning via overrides /
ignore-scriptsevasionCVE-2024-53866 / GHSA-vm32-9rqf-rh3r
More information
Details
Summary
pnpm seems to mishandle overrides and global cache:
This can make workspace A (even running with
ignore-scripts=true) posion global cache and execute scripts in workspace BUsers generally expect
ignore-scriptsto be sufficient to prevent immediate code execution on install (e.g. when the tree is just repacked/bundled without executing it).Here, that expectation is broken
Details
See PoC.
In it, overrides from a single run of A get leaked into e.g.
~/Library/Caches/pnpm/metadata/registry.npmjs.org/rimraf.jsonand persistently affect all other projects using the cachePoC
Postinstall code used in PoC is benign and can be inspected in https://www.npmjs.com/package/ponyhooves?activeTab=code, it's just a
console.logOn mac:
rm -rf ~/Library/Caches/pnpm ~/Library/pnpm/storeThis step is not required in general, but we'll be using a popular package for PoC that's likely cached
A/package.json: Install it withpnpm i --ignore-scripts(the flag is not required, but the point of the demo is to show that it doesn't help)B/package.json: Install it withpnpm iResult:
Also, that code got leaked into another project and it's lockfile now!
Impact
Global state integrity is lost via operations that one would expect to be secure, enabling subsequently running arbitrary code execution on installs
As a work-around, use separate cache and store dirs in each workspace
Severity
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:P/VC:N/VI:L/VA:N/SC:H/SI:H/SA:HReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
pnpm uses the md5 path shortening function causes packet paths to coincide, which causes indirect packet overwriting
CVE-2024-47829 / GHSA-8cc4-rfj6-fhg4
More information
Details
The path shortening function is used in pnpm:
However, it uses the md5 function as a path shortening compression function, and if a collision occurs, it will result in the same storage path for two different libraries. Although the real names are under the package name /node_modoules/, there are no version numbers for the libraries they refer to.

In the diagram, we assume that two packages are called packageA and packageB, and that the first 90 digits of their package names must be the same, and that the hash value of the package names with versions must be the same. Then C is the package that they both reference, but with a different version number. (npm allows package names up to 214 bytes, so constructing such a collision package name is obvious.)
Then hash(packageA@1.2.3)=hash(packageB@3.4.5). This results in the same path for the installation, and thus under the same directory. Although the package names under node_modoules are the full paths again, they are shared with C.
What is the exact version number of C?
In our local tests, it depends on which one is installed later. If packageB is installed later, the C version number will change to 2.0.0. At this time, although package A requires the C@1.0.0 version, package. json will only work during installation, and will not affect the actual operation.
We did not receive any installation error issues from pnpm during our local testing, nor did we use force, which is clearly a case that can be triggered.
For a package with a package name + version number longer than 120, another package can be constructed to introduce an indirect reference to a lower version, such as one with some known vulnerability.
Alternatively, it is possible to construct two packages with more than 120 package names + version numbers.
This is clearly an advantage for those intent on carrying out supply chain attacks.
The solution:
The repair cost is also very low, just need to upgrade the md5 function to sha256.
Severity
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:L/A:LReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
Configuration
📅 Schedule: (in timezone Europe/Stockholm)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about these updates again.
This PR has been generated by Mend Renovate.
Visa kommandoradsinstruktioner
Checka ut
Checka ut en ny gren från din projektkatalog och testa ändringarna.