This PR contains the following updates:
GitHub Vulnerability Alerts
Summary
pnpm seems to mishandle overrides and global cache:
- Overrides from one workspace leak into npm metadata saved in global cache
- npm metadata from global cache affects other workspaces
- installs by default don't revalidate the data (including on first lockfile generation)
This can make workspace A (even running with ignore-scripts=true) posion global cache and execute scripts in workspace B
Users generally expect ignore-scripts to 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.json and persistently affect all other projects using the cache
PoC
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.log
- Remove store and cache
On mac: rm -rf ~/Library/Caches/pnpm ~/Library/pnpm/store
This step is not required in general, but we'll be using a popular package for PoC that's likely cached
- Create
A/package.json:
{
"name": "A",
"pnpm": { "overrides": { "rimraf>glob": "npm:ponyhooves@1" } },
"dependencies": { "rimraf": "6.0.1" }
}
Install it with pnpm i --ignore-scripts (the flag is not required, but the point of the demo is to show that it doesn't help)
- Create
B/package.json:
{
"name": "B",
"dependencies": { "rimraf": "6.0.1" }
}
Install it with pnpm i
Result:
Packages: +3
+++
Progress: resolved 3, reused 3, downloaded 0, added 3, done
node_modules/.pnpm/ponyhooves@1.0.1/node_modules/ponyhooves: Running postinstall script, done in 51ms
dependencies:
+ rimraf 6.0.1
Done in 1.4s
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
The path shortening function is used in pnpm:
export function depPathToFilename (depPath: string, maxLengthWithoutHash: number): string {
let filename = depPathToFilenameUnescaped(depPath).replace(/[\\/:*?"<>|]/g, '+')
if (filename.includes('(')) {
filename = filename
.replace(/\)$/, '')
.replace(/(\)\()|\(|\)/g, '_')
}
if (filename.length > maxLengthWithoutHash || filename !== filename.toLowerCase() && !filename.startsWith('file+')) {
return `${filename.substring(0, maxLengthWithoutHash - 27)}_${createBase32Hash(filename)}`
}
return filename
}
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.
Schematic picture
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.
Release Notes
pnpm/pnpm (pnpm)
Compare Source
Major Changes
-
Lifecycle scripts of dependencies are not executed during installation by default! This is a breaking change aimed at increasing security. In order to allow lifecycle scripts of specific dependencies, they should be listed in the pnpm.onlyBuiltDependencies field of package.json #8897. For example:
{
"pnpm": {
"onlyBuiltDependencies": ["fsevents"]
}
}
-
pnpm link behavior updated:
The pnpm link command now adds overrides to the root package.json.
- In a workspace: The override is added to the root of the workspace, linking the dependency to all projects in the workspace.
- Global linking: To link a package globally, run
pnpm link from the package’s directory. Previously, you needed to use pnpm link -g.
Related PR: #8653
-
Secure hashing with SHA256:
Various hashing algorithms have been updated to SHA256 for enhanced security and consistency:
- Long paths inside
node_modules/.pnpm are now hashed with SHA256.
- Long peer dependency hashes in the lockfile now use SHA256 instead of MD5. (This affects very few users since these are only used for long keys.)
- The hash stored in the
packageExtensionsChecksum field of pnpm-lock.yaml is now SHA256.
- The side effects cache keys now use SHA256.
- The pnpmfile checksum in the lockfile now uses SHA256 (#8530).
-
Configuration updates:
-
manage-package-manager-versions: enabled by default. pnpm now manages its own version based on the packageManager field in package.json by default.
-
public-hoist-pattern: nothing is hoisted by default. Packages containing eslint or prettier in their name are no longer hoisted to the root of node_modules. Related Issue: #8378
-
Upgraded @yarnpkg/extensions to v2.0.3. This may alter your lockfile.
-
virtual-store-dir-max-length: the default value on Windows has been reduced to 60 characters.
-
Reduced environment variables for scripts:
During script execution, fewer npm_package_* environment variables are set. Only name, version, bin, engines, and config remain.
Related Issue: #8552
-
All dependencies are now installed even if NODE_ENV=production. Related Issue: #8827
-
Changes to the global store:
-
Store version bumped to v10.
-
Some registries allow identical content to be published under different package names or versions. To accommodate this, index files in the store are now stored using both the content hash and package identifier.
This approach ensures that we can:
- Validate that the integrity in the lockfile corresponds to the correct package, which might not be the case after a poorly resolved Git conflict.
- Allow the same content to be referenced by different packages or different versions of the same package.
Related PR: #8510
Related Issue: #8204
-
More efficient side effects indexing. The structure of index files in the store has changed. Side effects are now tracked more efficiently by listing only file differences rather than all files.
Related PR: #8636
-
A new index directory stores package content mappings. Previously, these files were in files.
-
Other breaking changes:
- The
# character is now escaped in directory names within node_modules/.pnpm.
Related PR: #8557
- Running
pnpm add --global pnpm or pnpm add --global @​pnpm/exe now fails with an error message, directing you to use pnpm self-update instead.
Related PR: #8728
- Dependencies added via a URL now record the final resolved URL in the lockfile, ensuring that any redirects are fully captured.
Related Issue: #8833
- The
pnpm deploy command now only works in workspaces that have inject-workspace-packages=true. This limitation is introduced to allow us to create a proper lockfile for the deployed project using the workspace lockfile.
- Removed conversion from lockfile v6 to v9. If you need v6-to-v9 conversion, use pnpm CLI v9.
pnpm test now passes all parameters after the test keyword directly to the underlying script. This matches the behavior of pnpm run test. Previously you needed to use the -- prefix.
Related PR: #8619
-
node-gyp updated to version 11.
-
pnpm deploy now tries creating a dedicated lockfile from a shared lockfile for deployment. It will fallback to deployment without a lockfile if there is no shared lockfile or force-legacy-deploy is set to true.
Minor Changes
-
Added support for a new type of dependencies called "configurational dependencies". These dependencies are installed before all the other types of dependencies (before "dependencies", "devDependencies", "optionalDependencies").
Configurational dependencies cannot have dependencies of their own or lifecycle scripts. They should be added using exact version and the integrity checksum. Example:
{
"pnpm": {
"configDependencies": {
"my-configs": "1.0.0+sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="
}
}
}
Related RFC: #8.
Related PR: #8915.
-
New settings:
-
New verify-deps-before-run setting. This setting controls how pnpm checks node_modules before running scripts:
install: Automatically run pnpm install if node_modules is outdated.
warn: Print a warning if node_modules is outdated.
prompt: Prompt the user to confirm running pnpm install if node_modules is outdated.
error: Throw an error if node_modules is outdated.
false: Disable dependency checks.
Related Issue: #8585
-
New inject-workspace-packages setting enables hard-linking all local workspace dependencies instead of symlinking them. Previously, this could be achieved using dependenciesMeta[].injected, which remains supported.
Related PR: #8836
-
Faster repeat installs:
On repeated installs, pnpm performs a quick check to ensure node_modules is up to date.
Related PR: #8838
-
pnpm add integrates with default workspace catalog:
When adding a dependency, pnpm add checks the default workspace catalog. If the dependency and version requirement match the catalog, pnpm add uses the catalog: protocol. Without a specified version, it matches the catalog’s version. If it doesn’t match, it falls back to standard behavior.
Related Issue: #8640
-
pnpm dlx now resolves packages to their exact versions and uses these exact versions for cache keys. This ensures pnpm dlx always installs the latest requested packages.
Related PR: #8811
-
No node_modules validation on certain commands. Commands that should not modify node_modules (e.g., pnpm install --lockfile-only) no longer validate or purge node_modules.
Related PR: #8657
Compare Source
Patch Changes
- Fix running pnpm CLI from pnpm CLI on Windows when the CLI is bundled to an executable #8971.
Platinum Sponsors
Gold Sponsors
Compare Source
Patch Changes
pnpm self-update should always update the version in the packageManager field of package.json.
- The pnpm CLI process should not stay hanging, when
--silent reporting is used.
- When
--loglevel is set to error, don't show installation summary, execution time, and big tarball download progress.
- Don't show info output when
--loglevel=error is used.
Platinum Sponsors
Gold Sponsors
Compare Source
Patch Changes
pnpm self-update should not leave a directory with a broken pnpm installation if the installation fails.
- Allow scope registry CLI option without
--config. prefix such as --@​scope:registry=https://scope.example.com/npm #9089.
pnpm self-update should not read the pnpm settings from the package.json file in the current working directory.
pnpm update -i should list only packages that have newer versions #9206.
- Fix a bug causing entries in the
catalogs section of the pnpm-lock.yaml file to be removed when dedupe-peer-dependents=false on a filtered install. #9112
Platinum Sponsors
Gold Sponsors
Compare Source
Patch Changes
- Fix instruction for updating pnpm with corepack #9101.
- Print pnpm's version after the execution time at the end of the console output.
- The pnpm version specified by
packageManager cannot start with v.
- Fix a bug causing catalog snapshots to be removed from the
pnpm-lock.yaml file when using --fix-lockfile and --filter. #8639
- Fix a bug causing catalog protocol dependencies to not re-resolve on a filtered install #8638.
Compare Source
Patch Changes
- Verify that the package name is valid when executing the publish command.
- When running
pnpm install, the preprepare and postprepare scripts of the project should be executed #8989.
- Quote args for scripts with shell-quote to support new lines (on POSIX only) #8980.
- Proxy settings should be respected, when resolving Git-hosted dependencies #6530.
- Replace
strip-ansi with the built-in util.stripVTControlCharacters #9009.
Platinum Sponsors
Gold Sponsors
Compare Source
Patch Changes
- Ensure that recursive
pnpm update --latest <pkg> updates only the specified package, with dedupe-peer-dependents=true.
Platinum Sponsors
Gold Sponsors
Compare Source
Patch Changes
- Fixed the Regex used to find the package manifest during packing #8938.
pnpm update --filter <pattern> --latest <pkg> should only change the specified package for the specified workspace, when dedupe-peer-dependents is set to true #8877.
- Exclude
.DS_Store file at patch-commit #8922.
- Fix a bug in which
pnpm patch is unable to bring back old patch without specifying @version suffix #8919.
Platinum Sponsors
Gold Sponsors
Compare Source
Patch Changes
- Fixed
publish/pack error with workspace dependencies with relative paths #8904. It was broken in v9.4.0 (398472c).
- Use double quotes in the command suggestion by
pnpm patch on Windows #7546.
- Do not fall back to SSH, when resolving a git-hosted package if
git ls-remote works via HTTPS #8906.
- Improve how packages with blocked lifecycle scripts are reported during installation. Always print the list of ignored scripts at the end of the output. Include a hint about how to allow the execution of those packages.
Platinum Sponsors
Gold Sponsors
Compare Source
Patch Changes
pnpm remove should not link dependencies from the workspace, when link-workspace-packages is set to false #7674.
- Installation with hoisted
node_modules should not fail, when a dependency has itself in its own peer dependencies #8854.
Platinum Sponsors
Gold Sponsors
Configuration
📅 Schedule: Branch creation - "" (UTC), Automerge - At any time (no schedule defined).
🚦 Automerge: Enabled.
♻ Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.
Uh oh!
There was an error while loading. Please reload this page.
This PR contains the following updates:
8.15.7->10.0.0GitHub Vulnerability Alerts
CVE-2024-53866
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:{ "name": "A", "pnpm": { "overrides": { "rimraf>glob": "npm:ponyhooves@1" } }, "dependencies": { "rimraf": "6.0.1" } }pnpm 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:{ "name": "B", "dependencies": { "rimraf": "6.0.1" } }pnpm 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
CVE-2024-47829
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.
Schematic picture
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.
Release Notes
pnpm/pnpm (pnpm)
v10.0.0Compare Source
Major Changes
Lifecycle scripts of dependencies are not executed during installation by default! This is a breaking change aimed at increasing security. In order to allow lifecycle scripts of specific dependencies, they should be listed in the
pnpm.onlyBuiltDependenciesfield ofpackage.json#8897. For example:{ "pnpm": { "onlyBuiltDependencies": ["fsevents"] } }pnpm linkbehavior updated:The
pnpm linkcommand now adds overrides to the rootpackage.json.pnpm linkfrom the package’s directory. Previously, you needed to usepnpm link -g.Related PR: #8653
Secure hashing with SHA256:
Various hashing algorithms have been updated to SHA256 for enhanced security and consistency:
node_modules/.pnpmare now hashed with SHA256.packageExtensionsChecksumfield ofpnpm-lock.yamlis now SHA256.Configuration updates:
manage-package-manager-versions: enabled by default. pnpm now manages its own version based on thepackageManagerfield inpackage.jsonby default.public-hoist-pattern: nothing is hoisted by default. Packages containingeslintorprettierin their name are no longer hoisted to the root ofnode_modules. Related Issue: #8378Upgraded
@yarnpkg/extensionsto v2.0.3. This may alter your lockfile.virtual-store-dir-max-length: the default value on Windows has been reduced to 60 characters.Reduced environment variables for scripts:
During script execution, fewer
npm_package_*environment variables are set. Onlyname,version,bin,engines, andconfigremain.Related Issue: #8552
All dependencies are now installed even if
NODE_ENV=production. Related Issue: #8827Changes to the global store:
Store version bumped to v10.
Some registries allow identical content to be published under different package names or versions. To accommodate this, index files in the store are now stored using both the content hash and package identifier.
This approach ensures that we can:
Related PR: #8510
Related Issue: #8204
More efficient side effects indexing. The structure of index files in the store has changed. Side effects are now tracked more efficiently by listing only file differences rather than all files.
Related PR: #8636
A new
indexdirectory stores package content mappings. Previously, these files were infiles.Other breaking changes:
#character is now escaped in directory names withinnode_modules/.pnpm.Related PR: #8557
pnpm add --global pnpmorpnpm add --global @​pnpm/exenow fails with an error message, directing you to usepnpm self-updateinstead.Related PR: #8728
Related Issue: #8833
pnpm deploycommand now only works in workspaces that haveinject-workspace-packages=true. This limitation is introduced to allow us to create a proper lockfile for the deployed project using the workspace lockfile.pnpm testnow passes all parameters after thetestkeyword directly to the underlying script. This matches the behavior ofpnpm run test. Previously you needed to use the--prefix.Related PR: #8619
node-gypupdated to version 11.pnpm deploynow tries creating a dedicated lockfile from a shared lockfile for deployment. It will fallback to deployment without a lockfile if there is no shared lockfile orforce-legacy-deployis set totrue.Minor Changes
Added support for a new type of dependencies called "configurational dependencies". These dependencies are installed before all the other types of dependencies (before "dependencies", "devDependencies", "optionalDependencies").
Configurational dependencies cannot have dependencies of their own or lifecycle scripts. They should be added using exact version and the integrity checksum. Example:
{ "pnpm": { "configDependencies": { "my-configs": "1.0.0+sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==" } } }Related RFC: #8.
Related PR: #8915.
New settings:
New
verify-deps-before-runsetting. This setting controls howpnpmchecksnode_modulesbefore running scripts:install: Automatically runpnpm installifnode_modulesis outdated.warn: Print a warning ifnode_modulesis outdated.prompt: Prompt the user to confirm runningpnpm installifnode_modulesis outdated.error: Throw an error ifnode_modulesis outdated.false: Disable dependency checks.Related Issue: #8585
New
inject-workspace-packagessetting enables hard-linking all local workspace dependencies instead of symlinking them. Previously, this could be achieved usingdependenciesMeta[].injected, which remains supported.Related PR: #8836
Faster repeat installs:
On repeated installs,
pnpmperforms a quick check to ensurenode_modulesis up to date.Related PR: #8838
pnpm addintegrates with default workspace catalog:When adding a dependency,
pnpm addchecks the default workspace catalog. If the dependency and version requirement match the catalog,pnpm adduses thecatalog:protocol. Without a specified version, it matches the catalog’s version. If it doesn’t match, it falls back to standard behavior.Related Issue: #8640
pnpm dlxnow resolves packages to their exact versions and uses these exact versions for cache keys. This ensurespnpm dlxalways installs the latest requested packages.Related PR: #8811
No
node_modulesvalidation on certain commands. Commands that should not modifynode_modules(e.g.,pnpm install --lockfile-only) no longer validate or purgenode_modules.Related PR: #8657
v9.15.9: pnpm 9.15.9Compare Source
Patch Changes
Platinum Sponsors
Gold Sponsors
v9.15.8: pnpm 9.15.8Compare Source
Patch Changes
pnpm self-updateshould always update the version in thepackageManagerfield ofpackage.json.--silentreporting is used.--loglevelis set toerror, don't show installation summary, execution time, and big tarball download progress.--loglevel=erroris used.Platinum Sponsors
Gold Sponsors
v9.15.7: pnpm 9.15.7Compare Source
Patch Changes
pnpm self-updateshould not leave a directory with a broken pnpm installation if the installation fails.--config.prefix such as--@​scope:registry=https://scope.example.com/npm#9089.pnpm self-updateshould not read the pnpm settings from thepackage.jsonfile in the current working directory.pnpm update -ishould list only packages that have newer versions #9206.catalogssection of thepnpm-lock.yamlfile to be removed whendedupe-peer-dependents=falseon a filtered install. #9112Platinum Sponsors
Gold Sponsors
v9.15.6: pnpm 9.15.6Compare Source
Patch Changes
packageManagercannot start withv.pnpm-lock.yamlfile when using--fix-lockfileand--filter. #8639v9.15.5: pnpm 9.15.5Compare Source
Patch Changes
pnpm install, thepreprepareandpostpreparescripts of the project should be executed #8989.strip-ansiwith the built-inutil.stripVTControlCharacters#9009.Platinum Sponsors
Gold Sponsors
v9.15.4: pnpm 9.15.4Compare Source
Patch Changes
pnpm update --latest <pkg>updates only the specified package, withdedupe-peer-dependents=true.Platinum Sponsors
Gold Sponsors
v9.15.3: pnpm 9.15.3Compare Source
Patch Changes
pnpm update --filter <pattern> --latest <pkg>should only change the specified package for the specified workspace, whendedupe-peer-dependentsis set totrue#8877..DS_Storefile atpatch-commit#8922.pnpm patchis unable to bring back old patch without specifying@versionsuffix #8919.Platinum Sponsors
Gold Sponsors
v9.15.2: pnpm 9.15.2Compare Source
Patch Changes
publish/packerror with workspace dependencies with relative paths #8904. It was broken inv9.4.0(398472c).pnpm patchon Windows #7546.git ls-remoteworks via HTTPS #8906.Platinum Sponsors
Gold Sponsors
v9.15.1: pnpm 9.15.1Compare Source
Patch Changes
pnpm removeshould not link dependencies from the workspace, whenlink-workspace-packagesis set tofalse#7674.node_modulesshould not fail, when a dependency has itself in its own peer dependencies #8854.Platinum Sponsors
Gold Sponsors
Configuration
📅 Schedule: Branch creation - "" (UTC), Automerge - At any time (no schedule defined).
🚦 Automerge: Enabled.
♻ Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.