pnpm 11.8.0 shipped on June 18, 2026, three days after 11.7.0, the release that added --frozen-store, pacquet resolution delegation, and a lockfile path-traversal fix. Where 11.7 was about reproducible and read-only installs, 11.8 is about observability and supply-chain reporting: a dry-run mode that finally mirrors npm, a Node.js package-map format that feeds the runtime's resolution experiments, and two CycloneDX SBOM improvements that make pnpm sbom usable for real compliance workflows. There is also a second path-traversal advisory, this time in configDependencies, and a macOS Gatekeeper fix that has been annoying native-module users for a while.
pnpm install --dry-run: the feature npm had and pnpm did not
The headline addition is --dry-run for pnpm install. It runs a full dependency resolution against the current manifests and prints what an install would add, remove, or change, then writes nothing: no pnpm-lock.yaml, no node_modules, no store mutation. It always exits with code 0, which matches the preview semantics of npm install --dry-run and closes issue #7340, which had been open since 2022.
The practical use is CI and code review: a PR that bumps a dependency or switches a catalog entry can run pnpm install --dry-run to surface the full transitive diff, including peer warnings and build-script approvals, without touching the working tree. Because pnpm's resolution is already fast, the cost is one resolution pass with no filesystem write. The exit code is deliberately always 0 so the flag can sit inside an existing install step without flipping a pipeline red on a benign diff.
Node.js package maps at node_modules/.package-map.json
11.8 can now generate a node_modules/.package-map.json during isolated (the default) and hoisted installs. Two settings control the behavior:
node-experimental-package-map: when enabled, pnpm injects the generated map into the Node.js script environments it manages, so the runtime can consult a precomputed package layout instead of walkingnode_modulesat startup.node-package-map-type: selects between astandardmap and aloosemap, trading strictness for compatibility.
This is early-stage plumbing for Node's broader package-resolution reform, where a declarative map file can replace the implicit, filesystem-driven resolution that every package manager currently has to work around. pnpm generating the map means projects that opt in get a consistent layout description whether they use the isolated or hoisted linker. The setting name (node-experimental-package-map) signals that the format is not stable yet.
SBOM: devDependencies scope and per-package generation
The two SBOM changes in 11.8 target the gap between a dependency tree and what a CycloneDX consumer can actually reason about.
First, pnpm sbom now marks components reachable only through devDependencies with CycloneDX scope: "excluded" plus the cdx:npm:package:development property. The excluded scope is the CycloneDX way of documenting "component usage for test and other non-runtime purposes", which is exactly what a devDependency is. The property mirrors the marker that @cyclonedx/cyclonedx-npm emits, so both modern (scope-based) and existing (property-based) consumers pick it up. Components reachable at runtime, including installed optionalDependencies, omit scope and default to required.
Second, per-package SBOM generation lands via two flags:
--out out/%s.cdx.jsonwrites one CycloneDX document per workspace package to individual files.--splitemits NDJSON to stdout, one package per line.
When --filter selects a single package, the SBOM root component now uses that package's metadata. Workspace inter-dependencies declared through the workspace: protocol, along with their transitive dependencies, are included so a per-package SBOM is self-contained. Author, repository, and license fall back to the root manifest when the package does not define them. For a monorepo that needs to ship an SBOM per published library, this is the difference between post-processing one giant document and getting per-package artifacts directly from the install tool.
configDependencies path traversal (GHSA-qrv3-253h-g69c)
11.8 closes a second path-traversal advisory, distinct from the lockfile alias fix in 11.7 and the esbuild 0.28.1 Windows traversal that surfaced in a different tool. Before 11.8, a committed lockfile whose configDependencies carried a traversal-shaped name (such as ../../PWNED) or version (such as ../../../PWNED) could cause pnpm install to create symlinks or write package files outside node_modules/.pnpm-config and the store.
The fix validates configDependencies names and versions before they are used to build filesystem paths. Names must now be valid npm package names and versions must be exact semver strings. The same validation runs on optional subdependencies of config dependencies and on the legacy workspace-manifest format, before any lockfile is written. Like the 11.7 lockfile gate, this is defense in depth: a project that already trusts its lockfile source still benefits, because the check protects against partial corruption and against bugs in the lockfile generator itself. See GHSA-qrv3-253h-g69c.
macOS Gatekeeper stops blocking native binaries
A quieter but widely felt fix: when pnpm imports files from its content-addressable store into node_modules, macOS preserves extended attributes, including com.apple.quarantine. If that xattr was present on a store blob (for example, it was first written under a Gatekeeper-enabled app such as a Git client), it propagated to node_modules, and Gatekeeper blocked the native binary from loading even though pnpm had already verified the file's integrity against the lockfile.
After importing a package, 11.8 now strips com.apple.quarantine from native binaries (.node, .dylib, .so), matching Homebrew's behaviour of dropping quarantine from verified downloads. The cleanup is macOS-only, runs in a single batched xattr call per package, is restricted to native binaries so other files are untouched, and is non-fatal. It fixes #11056.
Other fixes worth knowing
pnpm run --no-bail now exits with a non-zero code when any executed script fails, while still running every matched script to completion. This closes issue #8013: non-recursive --no-bail runs previously always exited 0 even on failure, which was inconsistent with recursive runs that already failed at the end.
pnpm view without a package name now searches upward for the nearest project manifest (package.json, package.yaml, or package.json5) and uses its name field, replacing the find-up dependency with the faster empathic module. Several lockfile-correctness bugs land too: incremental installs no longer keep duplicate transitive dependencies that a fresh install would not produce (#5108), optimisticRepeatInstall no longer reports "Already up to date" when only the lockfile changed (#12100), and catalog overrides that resolve through a catalog stay in sync with the catalog during pnpm update. pnpm version --recursive now honors the workspace filter instead of bumping every package (#11348). Reporter output for pnpm store and pnpm config now goes to stderr, so scripts like PNPM_STORE=$(pnpm store path) stop capturing warnings in their result.
Upgrade
pnpm 11.8.0 requires Node.js 22.13 or later, the same baseline as 11.7. The lockfile format is unchanged, so an npm install -g pnpm@latest (or corepack prepare pnpm@latest --activate) and a pnpm install is the full upgrade path. The new flags are all opt-in, and the configDependencies validation only rejects inputs that were never valid package names.



