pnpm 11.7.0 shipped on June 15, 2026, four days after the 11.6.0 security release that fixed the .npmrc environment-variable expansion vulnerability (GHSA-3qhv-2rgh-x77r). The 11.7 line picks up where 11.6 left off on the supply-chain hardening story and adds three features that change how teams run pnpm in containerized and reproducible-build environments: a --frozen-store install mode for read-only filesystems, full dependency-resolution delegation to the pacquet Rust port, and an opt-in batch mode for pnpm publish --recursive. There is also a real lockfile security fix that closes a path-traversal edge case the June 2026 npm supply chain attack retrospective flagged as a recurring class of bug across the ecosystem.
frozenStore: installs against a read-only package store
The headline feature of 11.7 is frozenStore (config key) and --frozen-store (CLI flag), an install mode for environments where the package store is on a read-only filesystem: a Nix store, an OCI image layer, a read-only bind mount, or a dm-verity rootfs. The store's SQLite index.db is opened with the immutable=1 URI, which bypasses the WAL/-shm sidecar creation that would otherwise fail with EROFS on a read-only directory. Every store-write path is suppressed: the index.db writer, the project-registry write, the side-effects cache, and the chmod that normally makes a bin executable when it crosses a read/write boundary.
The intended pairing is --offline --frozen-lockfile --frozen-store against a fully-populated store. Under the global virtual store (the default since 9.x), package directories live inside the store, so if the store is missing the build output of a package whose lifecycle scripts are approved (or that has a pnpm patch applied), pnpm fails up front with ERR_PNPM_FROZEN_STORE_NEEDS_BUILD rather than crashing mid-build on a read-only write. If the store is missing its content directory entirely, the install fails fast with ERR_PNPM_FROZEN_STORE_INCOMPLETE rather than attempting to initialize it.
There are two hard constraints to be aware of. The immutable=1 URI requires Node.js 22.15.0, 23.11.0, or 24.0.0 or later; on older runtimes, --frozen-store fails with a clear ERR_PNPM_FROZEN_STORE_UNSUPPORTED_NODE error. And --frozen-store is incompatible with --force and with a configured pnpr server, since both write into the store. Bin-linking also tolerates a read-only store: under the global virtual store, a package's bin source lives inside the store, so the chmod that makes it executable would be refused. With EPERM/EACCES or with EROFS on a genuinely read-only filesystem, pnpm now skips the chmod when the bin source is already executable and has a normalized shebang, and otherwise still errors out. The chmod is redundant when the seed already ships its bins executable.
The result is a fully reproducible install that can be cached as a single artifact and reused across CI, local dev, and production without any write access to the store. For Nix and OCI users, this is the missing piece: pnpm 11.6 was already usable in those environments, but every install would attempt a WAL chmod or a sidecar shm write that would either fail or fall back to a slower code path. 11.7 makes the read-only case the explicit, supported mode.
pacquet now resolves dependencies, not just materializes
The second feature is a milestone for the pacquet Rust port of pnpm: dependency resolution joins materialization in the set of operations pacquet can do end-to-end. The new behavior is opt-in via configDependencies: when pacquet is declared in configDependencies and the installed pacquet is at least 0.11.7, a default non-frozen install (isolated nodeLinker, plain pnpm install) is delegated to pacquet in a single pass. pacquet reads the manifests, writes pnpm-lock.yaml, and creates node_modules. pnpm detects the capability from the installed pacquet's version; older releases keep the resolve-then-materialize split.
pnpm add, pnpm update, and pnpm remove still resolve in pnpm itself, because those commands have to mutate the manifests before any resolution can happen. After the manifest is mutated, pacquet materializes. The lockfile format does not change. This remains an opt-in preview of the Rust install engine, tracked under #11723. For projects already running pacquet in frozen-install mode, the change is invisible: the resolution and materialization are already a single pacquet invocation. For projects still running on the Node.js install engine, the upgrade is a no-op as long as pacquet is not in configDependencies.
Lockfile path-traversal and reserved alias rejection
The third headline item is a security fix in the lockfile verifier. Before 11.7, an attacker who could control a lockfile entry (a malicious postinstall, a tampered CI artifact, a poisoned peer dependency's resolved spec) could set a dependency alias to a path-traversal string like ../../../escape or to a reserved name like .bin, .pnpm, or node_modules. Under nodeLinker: hoisted, the alias was joined directly under node_modules, letting package files be written outside the install root or overwrite pnpm-owned layout. The exploit class is the same one the esbuild 0.28.1 Windows path traversal (GHSA-g7r4-m6w7-qqqr) and the March 2026 Axios npm supply chain attack each surfaced in a different tool.
The 11.7 fix adds two layers. The hoisted graph builder now validates each alias at the directory sink (safeJoinModulesDir), matching the validation pnpm already performs for manifest-sourced aliases. And the lockfile verification gate (verifyLockfileResolutions) runs an always-on, policy-independent check that rejects any importer or snapshot dependency alias that is not a valid package name, failing the install early, before any fetch or filesystem work, for every node linker at once. The check is conservative: any alias that is not a syntactically valid package name (no /, no .., no reserved segments) is rejected with a clear error.
For a normal project, the practical effect is that an existing lockfile continues to install as before, and a lockfile with a path-traversal or reserved alias (which would be a previously-crafted attack, not a normal entry) now fails the install with a clear error. The fix does not change the lockfile format. It is the kind of defense-in-depth that the 11.6.0 .npmrc advisory also embodied: a project that already trusts its lockfile source still benefits from the verifier, because the verifier protects against bugs in the lockfile generator and against partial corruption.
--batch for pnpm publish --recursive
The fourth feature is a small but practical one: pnpm publish --recursive --batch sends all selected packages to the registry in a single PUT /-/pnpm/v1/publish request instead of one request per package. The target registry has to implement the batch publish endpoint (pnpr does); registries that don't are reported with a clear ERR_PNPM_BATCH_PUBLISH_UNSUPPORTED error. The batch is processed all-or-nothing by pnpr: if any package in the batch fails validation, none of the packages are published. For monorepos that publish N packages per release, the wall-clock win on pnpm publish --recursive is roughly Nx.
Other fixes that affect day-to-day work
The 11.7 release also fixes several correctness and regression bugs that have been open since 11.6. The most notable is a Windows regression in pnpm add that produced Cannot destructure property 'manifest' of 'manifestsByPath[rootDir]' as it is undefined when running outside a workspace; the root cause was selectProjectByDir keying the ProjectsGraph by opts.dir instead of project.rootDir, so downstream manifestsByPath lookups missed when the two paths normalized differently (typically drive-letter casing). The pnpm patch-remove command also no longer removes files outside the configured patches directory. pnpm publish now respects strictSsl: false for self-signed certificates the same way pnpm install does. Git dependencies that point to a subdirectory of a repository (repo#commit&path:/sub/dir) keep their path in the lockfile again after an integrity-pin regression in 11.6. And the shared package child resolution is now deterministic when the same package is reached through multiple contexts, fixing a class of "missing peer" reports (#12358) where request timing decided the child context.
The pnpm update -i and pnpm audit --fix -i interactive prompts also got a UX fix: the summary line after pressing Enter used to print every selected choice's full table row (label, current/target versions, workspace, URL) joined by commas, producing a wall of text. The summary now lists only the selected package names (or vulnerability keys). This is a small thing, but it is the kind of polish that separates a 11.7 minor release from a 11.6 patch.



