pnpm 11.20
pnpm 11.20 fixes a package-substitution risk in projects that install from more than one registry: packages resolved from a named registry are now recorded in the lockfile under registry-qualified keys. It also adds a built-in npmjs: alias, stops empty proxy settings from failing installs, hardens pnpm rebuild against a malicious lockfile, and speeds up dependency resolution.
Cambios menores
Named registries are recorded in the lockfile
Security fix. This affects projects that install through a named registry alias on pnpm 11.1.0–11.19.x — an alias you configured under namedRegistries, or the built-in gh:, which needs no configuration. It is semi-breaking for them; see "If you use named registries" below.
The lockfile recorded no marker for which registry a package came from. Packages were keyed by name@version alone, so when two registries served the same name and version, both collapsed onto a single packages: entry and whichever resolved first decided the tarball that every consumer got. A package you expect from your private registry could be installed from a different registry that publishes the same name and version, with nothing in the lockfile to reveal it.
Packages resolved from a named registry are now recorded under registry-qualified keys — <name>@<registryName>:<version>, e.g. foo@work:1.0.0 — so each registry gets its own entry and the lockfile pins which one a dependency came from.
The lockfile format version is unchanged. Registry-qualified keys appear only for packages resolved from a named registry, so a project that installs nothing through an alias sees no difference, and older pnpm versions keep reading the file.
Two related changes come with it:
- Every alias the lockfile references must stay in
namedRegistries. Reading an entry whose alias is gone fails withERR_PNPM_MISSING_NAMED_REGISTRYrather than silently falling back to the default registry, since that would fetch a different package. Renaming an alias re-resolves the packages that used it. - An alias that shadows a reserved dependency specifier prefix (
file,link,workspace,runtime,npm,jsr, ...) is now rejected withERR_PNPM_RESERVED_NAMED_REGISTRY_NAMEinstead of being silently shadowed by the corresponding resolver.
Tarball URLs that follow the standard registry layout are no longer written to the lockfile for named-registry packages; they are recomputed from namedRegistries on demand.
pnpm licenses and pnpm sbom keep the two artifacts apart as well: license records carry the registry alias, and SBOM components carry the purl repository_url qualifier.
If you use named registries
Your next non-frozen install re-keys those entries, which shows up as a lockfile diff. Commit it — that diff is the fix being applied. Review it: an entry that moves to a registry you did not expect is worth investigating.
Everyone working on the project should be on this version or newer before you do. An older pnpm reads the re-keyed lockfile fine — frozen installs are unaffected — but it does not produce registry-qualified keys itself, so any install that updates the lockfile writes those entries back to the old shape, and the next install on a current pnpm re-qualifies them. The result is a lockfile that flips back and forth, and while it is in the old shape the project is exposed again. Because the lockfile format version is deliberately unchanged, pnpm cannot detect this and warn you about it.
There is no setting to keep the old behavior: the old shape is the vulnerability.
The built-in npmjs: alias
npmjs: now resolves to https://registry.npmjs.org/ with no configuration, alongside the existing gh: alias for GitHub Packages. It pins a dependency to the public registry even when registry points elsewhere, such as an internal proxy:
{
"dependencies": {
"left-pad": "npmjs:^1.3.0"
}
}
npm: cannot do this — it is the alias protocol (npm:<name>@<range>) and resolves through whatever registry points at.
If you mirror or proxy npmjs, point the alias at your mirror:
namedRegistries:
npmjs: https://npm.internal.example.com/
Built-in registry URLs are also the prefixes a lockfile's recorded tarball URL is matched against when pnpm verifies a package. Without the override, an entry whose tarball URL is on registry.npmjs.org is verified against the public registry rather than your mirror. This only affects lockfiles that record such URLs — a canonical URL for your configured registry is omitted from the lockfile and unaffected — and only when a tarball-URL, minimumReleaseAge, or trustPolicy check runs. Overriding the alias is the same escape hatch GHES users already have for gh.
Patch Changes
- An empty
http-proxy,https-proxy,proxy, orno-proxyvalue — from the.npmrc,pnpm-workspace.yaml, the CLI, or theHTTP_PROXY/HTTPS_PROXY/PROXY/NO_PROXYenvironment variables — no longer fails the install withERR_PNPM_INVALID_PROXY. Empty settings read as unset, so a shell exportingHTTP_PROXY=disables the proxy, and an emptyproxy=in the.npmrcno longer suppressesHTTPS_PROXY(#13533).proxy=falsein the.npmrcorproxy: falseinpnpm-workspace.yamlnow turns proxying off instead of being read as a proxy host namedfalse. - Security:
pnpm rebuildnow refuses a lockfile whosepackageskey carries a path traversal in the package name (e.g.../../../escaped@1.0.0), instead of running that package's lifecycle scripts and linking its bins in a directory outside the virtual store. Such a name is rejected withERR_PNPM_INVALID_DEPENDENCY_NAME. - The env lockfile no longer pins
@pnpm/exealongsidepnpmwhen the wanted pnpm version is 12 or newer. From v12 the unscopedpnpmpackage is itself the native executable, so@pnpm/exeis not published for it and resolving it would fail. - Fixed the order in which pnpm matches a lockfile's recorded tarball URL against known registry URLs. Two registry URLs of equal length were previously ordered arbitrarily, so which one a tarball URL matched could differ between runs.
- Dependency resolution is faster: package metadata is now filtered once per packument instead of once per dependency edge when
minimumReleaseAgeis active, and parsed semver versions and ranges are reused instead of re-parsed on every comparison.
