Repository navigation
Fix silent hosted pnpm pin beside a file: copy - #1469
Conversation
Assisted-by: Claude Code:claude-opus-5-5
Since #1008, a hosted scan of a pnpm workspace where one member uses the registry release and another a file: directory or tarball copy of the same name@version pinned the registry entry and said nothing. pnpm keeps installing the file: copy unpatched, and vex refuses to attest the pin, so the scan wrote wiring nothing could vouch for. #1008 moved such refs into Discovery::shadowed so rollback can still undo them, and the scan's veto counted them as attributed pins. The veto now treats a pin seen only as shadowed like contested wiring: the patch is skipped as redirect_unattributable, naming the file: entry, and the lock is left as found. Bundled, alias, vlt bundled and takeover pins keep their exemptions. Assisted-by: Claude Code:claude-opus-5-5
Reviewing the first fix showed that vetoing the whole patch was the wrong call. npm and yarn classic already pin the registry copy, which most of the app loads, and warn about the copy they can't reach. Skipping the patch on pnpm would leave the registry copy vulnerable too. So the veto is gone. A hosted scan now pins the registry entry as before and adds a redirect_copy_unpatched warning that names each same-lock unpatched copy (a pnpm file: directory or tarball dep) beside the pin. The warning is left out when the rewriter already reported that copy. CLI_CONTRACT.md lists the new code. Assisted-by: Claude Code:claude-opus-5-5
The same hosted run that pinned a pnpm registry entry beside a file: copy also attested the package not_affected in its own --vex, although that copy installs unpatched. The engine now records such pins' uuids with the skipped bundled copies, so the in-run VEX verifies them instead of assuming them applied. The redirect_copy_unpatched warning is limited to pnpm locks, where the rewriter can't see these copies, and is given once per lock and entry instead of being matched against other warnings' text. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 33b60e7. Configure here.
|
Labeled Ready for review by the burn-down agent.
Generated by Claude Code |
Final review briefWhat it does. In hosted Risk: low. It only adds a warning and makes the in-run VEX more conservative. Which pins are written, the veto logic and the exit codes don't change. The extra work is a filter over a discovery the engine already runs, with no new I/O. Look here
Verified
Changes I made: none. Open questions (non-blocking nits)
Auto-merge is armed: approving sends this straight to the merge queue. Generated by Claude Code |
* ci(release): publish npm from release.yml, not publish-npm.yml Every socket-patch npm package's trusted publisher is registered against release.yml, and npm allows only one publisher per package. The 5.0.0 release dispatched publish-npm.yml, whose OIDC token named that file, so npm rejected it with E401 before staging anything. Move the npm leg back inline as release.yml's npm-publish job (same steps as publish-npm.yml) and delete publish-npm.yml, so the existing registration keeps working. Add an `npm-retry-version` input that skips the version gate, build, tag, GitHub release and crates.io jobs and re-stages npm from an existing release's assets; this retries 5.0.0 now and replaces the manual Publish npm dispatch. crates.io stays dispatched through publish-cargo.yml, since crates.io allows several publishers per crate. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * chore(release): fold 22 more PRs into the 5.0.0 notes Cover the PRs merged to main after #1432's base (1779317..4cb3643): Cargo source replacement and registry = "crates-io" (#338/#455, #386), pnpm lockfile=false (#1074), berry removed descriptors (#1435), yarnrc ${VAR} reads (#1100), CRLF .npmrc append (#1196), Composer vendor-dir cascade and global home (#439/#686, #586), Go +incompatible vex (#484), gem moved BUNDLE_GEMFILE and commented Gemfile revert (#952, #988), Poetry source = "pypi" (#1282) and Hatch path characters (#547). No entries for #1411 (macOS fix to code #1393 added in this release; the #1393 entry already describes the intended behavior), #1426 (perf regression introduced and fixed within 5.0.0) or the CI/test/bench PRs. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * chore(release): cover #1419, #1434, #1440, #1442, #1444 in the 5.0.0 notes Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * chore(release): 5.0.1 Cut [Unreleased] into [5.0.1] — 2026-10-13 and stamp 5.0.1 with scripts/version-sync.sh. The 5.0.1 notes cover main through 501c6d9 (#1449), i.e. every PR after the v5.0.0 tag (6ad6add): the npm publish fix (5.0.0 never reached npm or crates.io, 5.0.1 is the first v5 there), #1465, #759, #1460, #1462, #1445, #869, #1447, #1450, #1467, and #1449 under Maintenance. CI/test-only PRs (#1448, #1452, #1454, #1457, #1459) have no entries. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * chore(release): 5.0.1 notes for PRs merged since the cut Covers #1469, #1470, #1474, #1475, #1478, #1479, #1480 and #1482; #1484 is CI-only. The notes now cover main through 285a5e8. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LLM Description written by Claude Code:claude-opus-5-5
Refs #935
This fixes the hosted half of #935. Vendored pnpm still writes its
override silently beside a
file:copy. That gap is in the pnpm vendorbackend, which has no post-write discovery check to hook into, so it stays
open on #935 as a follow-up (see below).
What users saw
Take a pnpm workspace where one member depends on registry
left-pad@1.3.0and another on a
file:directory or tarball copy of the same release.scan --mode hostedpinned the registry entry and reportedsuccesswith no warning about the
file:copy, which pnpm keeps installingunpatched. The same run's
--vexthen attested the packagenot_affected.A later standalone
vexcorrectly refused (patched_ref_unattributable).Bisected by the pnpm bug-hunt to f3c6313 (#1008).
Root cause
The hosted engine re-runs lockfile discovery over the rewrite before
writing (
unattributed_pinsinhosted/engine.rs). Before #1008, a pinwhose own lock also installs an unpatched copy of the same
name@versionwas dropped from discovery, so the engine skipped the patch as
redirect_unattributable. #1008 (for #828) moved such refs intoDiscovery::shadowedso rollback and remove can still undo them, andcounted them as pins, so the engine let the pin through silently.
The pnpm rewriter can't see these copies itself, because a directory
copy's version lives in its own
package.json, not in the lock. So nothingrecorded the uuid in
bundled_skipped_uuids, and the in-run VEX assumed itapplied.
Fix
The registry entry is still pinned, which matches npm
(
redirect_npm_non_registry_entry_skipped) and yarn classic(
redirect_yarn_classic_directory_skipped). The registry copy is what mostof the app loads, so skipping the whole patch would leave it vulnerable
too. For each shadowed hosted pin whose own pnpm lock carries a
file:copy (from discovery's
unpatched_copies), the engine now:redirect_copy_unpatchedwarning naming the lock and the entry,once per (lock, entry);
bundled_skipped_uuids, so the in-run--vexdoesnot attest it (the same mechanism the yarn classic rewriter uses).
CLI_CONTRACT.mddocuments the new warning code and updates theattribution-gate paragraph.
Review notes
/code-review highran twice.because it contradicts the npm and yarn classic policy of patching the
registry copy and warning about the other one. It also changed npm
behaviour without a test. Reworked in d14593d.
that dedup by matching warning text was fragile, and that the warning
wasn't scoped to pnpm. All three are fixed in this push: the uuid joins
bundled_skipped_uuids, the warning is limited to pnpm locks and keyedby (file, entry), and there's a new
--vextest.rewriter. The rewriter only sees lock text, and a directory copy's
version needs its
package.json. Discovery already reads that(
record_pnpm_file_copies), so the engine reuses it rather thanduplicating the read.
Tests
New in
crates/socket-patch-cli/tests/in_process_redirect_pnpm.rs:hosted_pnpm_file_directory_copy_is_namedhosted_pnpm_file_tarball_copy_is_named--vexdoesn't attesthosted_pnpm_file_copy_is_not_attested_by_the_in_run_vexnot_affectedstatement)in_process_redirect_pnpm: 35/35 pass.cargo fmt --checkandcargo clippy --workspace --all-features -D warnings: clean.cargo test --workspace --all-features: every target passes except 13 tests that fail because of this container (not checked againstmainhere). 12 are permission-based write-failure tests, which can't fail a write when the container runs as root. One is ane2e_bun_lockbtest whose rollback fails reaching patches-api.socket.dev (tunnel error). The same 13 failed before and after each change in this PR, and none of them is in the hosted pnpm or engine-gate path. CI is the check for them.Follow-up (stays on #935)
Vendored pnpm:
scan --mode vendoredstill writesoverrides: {left-pad@1.3.0: file:.socket/vendor/…}beside afile:copywithout a warning. npm and Bun vendored emit
vendor_non_registry_entry_skippedhere. The pnpm backend(
vendor/pnpm_lock.rspreflight) needs the same, reading the copy'sversion the way
record_pnpm_file_copiesdoes.🤖 Generated with Claude Code
https://claude.ai/code/session_01H7rWeixkY6z9hT2QNYmWLC
Generated by Claude Code