Skip to content

pnpm hosted → vendored takeover un-hosts the package before pnpm's vendored refusals run, so a catalog entry, a CRLF lock or a workspace exact-pin override leaves it unpatched in both modes #853

Description

[agent] Found by the scheduled pnpm bug-hunt routine (ledger #303).

Summary

scan --mode vendored over a hosted pnpm pin restores the pin to its upstream registry entry first, and only then runs the pnpm vendored backend. When the backend then refuses the package for a project-level or lock-shape reason that hosted mode handles fine, the package ends up in neither mode. The hosted pin is gone from pnpm-lock.yaml, nothing is vendored, and the next install gets the unpatched registry bytes.

Three ordinary shapes trigger it, and they're all documented vendored refusals:

  • a pnpm catalog dependency ("left-pad": "catalog:") → vendor_lock_entry_unsupported
  • a CRLF pnpm-lock.yaml (a core.autocrlf checkout; hosted mode handles CRLF) → vendor_lockfile_crlf_unsupported
  • a user exact-pin override in pnpm-workspace.yaml (overrides: { left-pad: 1.3.0 }) → vendor_override_conflict

The run does exit 1 / partial_failure, but the restore has already been written, so the project has lost its patch.

Impact

A project that is correctly hosted-patched and asks to switch to vendored loses its patch. Its committed lock now installs the vulnerable upstream release. Re-running hosted mode recovers, but nothing in the output says the package was un-hosted. The only hint is the vendor_takeover_reverted_redirect advisory next to the failure.

Repro (Linux, Node 22, main 4646693)

I used a local mock patch API that serves left-pad@1.3.0 with a marker prepended to index.js (hosted tarball, vendor grant, view, /registry mirror; SOCKET_PATCH_SERVER_URL / SOCKET_NPM_REGISTRY point at it).

API="--api-url http://127.0.0.1:8787 --org acme --api-token x"
mkdir p && cd p
echo '{"name":"c","version":"1.0.0","dependencies":{"left-pad":"catalog:"}}' > package.json
printf 'packages:\n  - .\ncatalog:\n  left-pad: 1.3.0\n' > pnpm-workspace.yaml
pnpm install
socket-patch scan --mode hosted --json --yes $API      # success, redirected 1; lock pins the hosted tarball
socket-patch scan --mode vendored --dry-run --json --yes $API   # exit 0, action would_vendor, no refusal predicted
socket-patch scan --mode vendored --json --yes $API    # exit 1, partial_failure:
#   vendor_takeover_reverted_redirect  (hosted pin restored to registry)
#   failed vendor_lock_entry_unsupported (catalog)
grep -c 127.0.0.1 pnpm-lock.yaml                       # 0: the hosted pin is gone
ls .socket/vendor 2>/dev/null                          # nothing vendored
rm -rf node_modules && pnpm install --frozen-lockfile  # exit 0, unpatched upstream left-pad

For the CRLF variant, use a plain "left-pad": "1.3.0" dependency and run sed -i 's/$/\r/' pnpm-lock.yaml after the hosted scan. For the override variant, use a plain dependency plus overrides:\n left-pad: 1.3.0 in pnpm-workspace.yaml.

Expected vs actual

Matrix (scan exit / hosted pins left in the lock / fresh pnpm install --frozen-lockfile)

pnpm catalog CRLF lock workspace exact-pin override control (plain dep)
9.15.9 1 / 0 / unpatched 1 / 0 / unpatched 1 / 0 / unpatched not run
10.34.5 1 / 0 / unpatched 1 / 0 / unpatched 1 / 0 / unpatched not run
11.28.3 1 / 0 / unpatched 1 / 0 / unpatched 1 / 0 / unpatched not run
12.8.1 1 / 0 / unpatched 1 / 0 / unpatched 1 / 0 / unpatched 0 / 0 / patched (vendored)

Every failing cell was reproduced at least twice (12.8.1 and 10.34.5 three times). I tested Linux only. The ordering is OS-independent, and the CRLF variant is the one Windows autocrlf users would hit.

Not a regression: release 4.0.0 behaves the same (catalog variant, pnpm 12.8.1: exit 1, vendor_lock_entry_unsupported after vendor_takeover_reverted_redirect, hosted pin gone).

Suspect code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions