Skip to content

fix(npm): detect pnpm node-linker=pnp trees as pnpm - #181

Open
Mikola Lysenko (mikolalysenko) wants to merge 1 commit into
mainfrom
fix/pnpm-pnp-linker-detection
Open

fix(npm): detect pnpm node-linker=pnp trees as pnpm#181
Mikola Lysenko (mikolalysenko) wants to merge 1 commit into
mainfrom
fix/pnpm-pnp-linker-detection

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Problem

Audit finding (2026-08-13, medium, mode: both, pm: pnpm): pnpm's own PnP mode (node-linker=pnp in .npmrc) writes a .pnp.cjs loader at the project root just like yarn-berry does, but keeps real package directories in the pnpm virtual store (node_modules/.pnpm/<name>@<ver>/node_modules/<name>) — exactly the layout the CoW guard patches natively. Both detection sites classified ANY PnP marker as yarn-berry PnP before checking pnpm markers:

  • detect_npm_pkg_manager (crawlers/pkg_managers.rs) returned YarnBerryPnP, so socket-patch apply refused outright (exit 1) and told the user to run yarn patch — a yarn command in a pnpm repo. The repo itself documented this as a real bug via the #[ignore]d RED test pnpm_pnp_mode_is_pnpm_not_yarn_berry.
  • detect_npm_lock_flavor (vendor/npm_flavor.rs) refused scan --mode vendored with vendor_yarn_berry_unsupported and the same wrong yarn patch remedy.

Concrete failing scenario: pnpm install with node-linker=pnp (layout verified against pnpm 10.28.2), then socket-patch apply → refusal with a yarn remedy on a tree socket-patch patches natively.

Fix

One shared pub(crate) helper, pkg_managers::pnpm_pnp_layout(), carves pnpm's PnP mode out of both PnP branches so the two detection sites can never drift on what counts as a pnpm-PnP tree. The reclassification requires ALL of: an installed pnpm store (node_modules/.modules.yaml or node_modules/.pnpm/), pnpm-lock.yaml at the root, and NO yarn.lock.

  • apply: such trees now detect as Pnpm and proceed under the existing CoW guard (informational note, no behavior change for real yarn-berry projects).
  • scan --mode vendored: still refuses fail-closed — the file: rewiring has no fixtures under pnpm's PnP linker — but with a dedicated code vendor_pnpm_pnp_unsupported and a pnpm-appropriate remedy (use scan --mode hosted, or switch .npmrc to node-linker=isolated and reinstall) instead of yarn patch.

Ambiguous trees stay the fail-closed yarn-berry refusal: a yarn.lock alongside the loader (mid-migration multi-PM repo), or a stale pnpm-lock.yaml with no installed store. These boundaries were already pinned by pre-existing guard tests (pnp_with_both_lockfiles_stays_yarn_berry, pnp_with_stale_pnpm_lockfile_only_stays_yarn_berry), which still pass unchanged.

No skipped findings — this lane had a single finding, fixed at both boundaries it names.

Testing

  • Un-ignored the pre-existing RED test crawlers::pkg_managers::tests::pnpm_pnp_mode_is_pnpm_not_yarn_berry (verified red on pre-fix code: left: YarnBerryPnP, right: Pnpm), now green.
  • New vendor::npm_flavor::tests::pnpm_pnp_layout_refuses_with_pnpm_remedy: asserts vendor_pnpm_pnp_unsupported, a node-linker=pnp diagnosis, the scan --mode hosted remedy, and no yarn patch text.
  • New vendor::npm_flavor::tests::pnpm_pnp_carve_out_stays_yarn_berry_when_ambiguous: yarn.lock alongside, and stale pnpm-lock.yaml without an installed store, both keep vendor_yarn_berry_unsupported.
  • Suites: cargo test -p socket-patch-core --lib (2078 passed), cargo test -p socket-patch-cli --test ecosystem_dispatch_e2e (22 passed), plus e2e_safety_yarn_pnp (20 passed; 3 ignored pre-existing RED tests for an unrelated scoping bug), e2e_safety_pnpm (27 passed), in_process_vendor (13 passed), crawler_npm_e2e (58 passed).

🤖 Generated with Claude Code


Note

Medium Risk
Changes package-manager and vendor lockfile detection on the safety-critical PnP branch; behavior is constrained by strict carve-out rules and existing guard tests, but wrong classification could still block apply or mis-route vendor errors.

Overview
Fixes misclassification of pnpm’s node-linker=pnp mode, which writes a root .pnp.cjs like yarn-berry but keeps real packages in the pnpm virtual store—layout apply already patches via CoW.

Adds shared pnpm_pnp_layout() (installed pnpm store + pnpm-lock.yaml + no yarn.lock) and uses it in both detect_npm_pkg_manager and detect_npm_lock_flavor so they stay aligned. apply now treats qualifying trees as pnpm instead of refusing with a yarn patch remedy. scan --mode vendored still fails closed but returns vendor_pnpm_pnp_unsupported with pnpm guidance (scan --mode hosted or node-linker=isolated); ambiguous cases (both lockfiles, stale lockfile without store) keep the yarn-berry refusal.

Un-ignores pnpm_pnp_mode_is_pnpm_not_yarn_berry and adds vendor tests for the new error and ambiguity guards.

Reviewed by Cursor Bugbot for commit b3b9fa4. Configure here.

pnpm's own PnP mode (node-linker=pnp in .npmrc) writes a .pnp.cjs
loader at the project root just like yarn-berry does, but keeps real
package directories in the pnpm virtual store — exactly the layout
the CoW guard patches natively. The detector returned YarnBerryPnP
for ANY PnP marker, so `apply` refused outright and told the user to
run `yarn patch` in a pnpm repo; the vendored probe had the same hole
one layer down and refused with vendor_yarn_berry_unsupported.

Add a shared carve-out (pnpm_pnp_layout): a PnP marker with
pnpm-lock.yaml, an installed pnpm store, and NO yarn.lock classifies
as pnpm, so `apply` proceeds under the CoW guard. Anything ambiguous
(yarn.lock alongside, or a stale pnpm-lock.yaml with no installed
store) keeps the fail-closed yarn-berry refusal. Vendored mode still
refuses on such trees — the file: rewiring has no fixtures under
pnpm's PnP linker — but now with its own code
(vendor_pnpm_pnp_unsupported) and a pnpm remedy: use
`scan --mode hosted` or switch node-linker and reinstall.

Un-ignores the RED test pnpm_pnp_mode_is_pnpm_not_yarn_berry and adds
vendored-probe twins for the pnpm refusal and both ambiguity guards.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants