Skip to content

pnpm hosted-to-vendored scan/get dry-run previews success for a refused takeover #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


Backlog review — 2026-10-08

Priority: P1 → P3. The failed-takeover state loss was fixed by #963; the remaining scan/get preview mismatch is low priority. Keep the active #1007 follow-up.

The title now describes the remaining scope after the partial fixes. The original report is preserved above for historical context.

Activity

  1. mikolalysenko commented on Oct 5, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Triage: priority:p1. pnpm (npm-family). Same shape as gem #775 (fix in open PR #776) and the yarn berry takeover preflight: vendor_records_reusing restores the hosted pin before the ecosystem's vendored refusals run. The pnpm side needs its own preflight. One trigger (workspace exact-pin override) is also #854.


    Generated by Claude Code

  2. mikolalysenko commented on Oct 6, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Shares root cause with #944: the hosted → vendored takeover in crates/socket-patch-cli/src/commands/vendor.rs restores the hosted pin (restore_upstream) before the target backend's vendored refusals run, and the pre-restore preflight only has gem and npm/berry cases (no pnpm, no PyPI). Will be fixed together.


    Generated by Claude Code

  3. mikolalysenko commented on Oct 6, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Claiming this issue (with #944; shared root cause: the hosted → vendored takeover in vendor.rs commits the upstream restore before the target vendored backend's refusals run). Branch: agent/fix-takeover-vendored-preflight. Claim-ID: 2026-10-06T20:21:01Z-4b53a1


    Generated by Claude Code

  4. mikolalysenko commented on Oct 6, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Draft PR: #963


    Generated by Claude Code

  5. mikolalysenko commented on Oct 6, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] #963 is ready for review and fixes the data loss. When the vendored backend refuses a hosted pnpm purl (catalog, CRLF lock, workspace override, or any other refusal), the takeover's upstream restore is now rolled back inside the run's group commit, so the hosted pin stays byte-for-byte and the purl fails with the backend's own code. vendor --dry-run also previews that refusal now.

    This issue stays open (the PR says Refs, not Fixes) for one remaining piece: the scan / get --mode vendored --dry-run preview is an offline ledger-only classification that doesn't model the takeover, so it still says would_vendor for these shapes. That will be a follow-up slice after #963.


    Generated by Claude Code

  6. 2 remaining items

  7. mikolalysenko commented on Oct 7, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] #963 is merged. A refused hosted → vendored takeover now keeps the hosted pnpm pin byte-for-byte, and vendor --dry-run previews the refusal.

    This issue stays open for one remaining piece: scan / get --mode vendored --dry-run still preview would_vendor for the catalog, CRLF and workspace-override shapes. I've released the agent:claimed label so a later run can take that follow-up slice.


    Generated by Claude Code

  8. mikolalysenko commented on Oct 7, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Re-verified on main 1c6c509 with real pnpm (pnpm bug-hunt run 29, ledger #303). The part fixed by #963 holds:

    pnpm shape scan --mode vendored hosted pin after refusal fresh --frozen-lockfile (dead registry, empty store)
    12.10.1 CRLF lock (2 hosted pkgs) exit 1, vendor_lockfile_crlf_unsupported ×2 lock + workspace file byte-identical both packages patched
    9.15.9 catalog: entry for left-pad, plain ms exit 1, left-pad → vendor_lock_entry_unsupported, ms vendored (vendor_takeover_reverted_redirect) left-pad keeps its hosted pin left-pad (hosted) and ms (vendored) patched
    11.28.5 workspace overrides: {left-pad: 1.3.0} exit 1, left-pad → vendor_override_conflict, ms vendored left-pad keeps its hosted pin both patched

    The remaining piece still reproduces as described: scan --mode vendored --dry-run reports would_vendor for both packages in the catalog and override cells (exit 0), although the real run refuses left-pad.


    Generated by Claude Code

  9. mikolalysenko commented on Oct 7, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Claiming this issue as part of the pnpm open-issue sweep in draft PR #1007. Branch: agent/fix-pnpm-open-issues. Claim-ID: 2026-10-07T12:44:29Z-pnpm07

  10. mikolalysenko commented on Oct 7, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] PR #1007 fixes this issue. It has a regression test that fails on main, CI is fully green (552 checks) and it's ready for review. The issue will close when #1007 merges. The PR description's table gives the root cause, the fix and the test for each issue.

  11. changed the title [-]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[/-] [+]pnpm hosted-to-vendored scan/get dry-run previews success for a refused takeover[/+] on Oct 8, 2026
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