Repository navigation
Agent-mode apply writes through node_modules links into first-party source (npm workspace members, file: deps, npm link targets), overwriting the user's code, and rollback restores upstream bytes instead #626
Description
Activity
- addedbugSomething isn't workingSomething isn't workingbughuntFound by a scheduled package-manager bug-hunt agentFound by a scheduled package-manager bug-hunt agentpm:npmnpmnpm
on Oct 3, 2026 - added a commit that references this issue
on Oct 3, 2026 mikolalysenko commented
on Oct 3, 2026 CollaboratorAuthorMore actions[agent] Claiming this issue (root cause: the npm crawler accepts any
node_modulessymlink in an importer tree as an installed copy without checking where it resolves, so links to workspace members,file:dirs andnpm linktargets are patched in place). Branch: agent/fix-npm-agent-first-party-links. Claim-ID: 2026-10-03T01:21:03Z-cdb289
Generated by Claude Code
mikolalysenko commented
on Oct 3, 2026 CollaboratorAuthorMore actions- added 7 commits that reference this issue
on Oct 3, 2026 mikolalysenko commented
on Oct 3, 2026 CollaboratorAuthorMore actions[agent] pnpm cells from the scheduled pnpm bug-hunt routine (ledger #303), following the npm routine's hand-over. Main
045d7ec, Linux, Node 22.Setup: a first-party
left-pad@1.3.0(module.exports = "first-party fork";), reached either as a workspace member throughworkspace:*(packages/app/node_modules/left-pad -> ../../left-pad) or as alink:./forkdependency. A local.socket/manifest.json+ blobs carries a patch that prepends a marker to upstreamleft-pad@1.3.0/index.js. Thensocket-patch apply --offline --json,vex --offlineandrollback --offline.pnpm workspace:*memberlink:dirfile:dir7.33.7 repro repro pass (pnpm copies it into .pnpm/left-pad@file+fork, and the fork is untouched)8.15.9 repro repro pass 9.15.9 repro (2/2) repro (2/2) pass 10.28.0 repro – – 11.28.3 repro repro pass 12.8.1 repro (2/2) repro (2/2) pass In each repro:
applygivessuccess, applied 1(plus a spuriousskipped 1, the duplicate visit from #633), and the committed fork now starts with/*SOCKET_PATCHED*/followed by the upstream file.vexsaysnot_affected. Afterrollbackthe file holds the upstream original (/* This program is free software…), so the user's code is gone.PR #634 head
cf1b6e9, same fixtures on pnpm 9.15.9 and 12.8.1,workspace:*andlink::partialFailurewithapply_failed("Refusing to patch …/packages/left-pad: node_modules links to it, but it is outside every node_modules tree…"), and the fork is untouched. So the PR covers pnpm too. pnpm'sfile:shape doesn't need the guard, because it's a store copy.
Generated by Claude Code
mikolalysenko commented
on Oct 4, 2026 CollaboratorAuthorMore actions[agent] Bun cells from the scheduled Bun bug-hunt routine (ledger #306). Main
045d7ec, Linux, real Bun installs, local mock patch API.Setup: a first-party
plain-pkg@1.0.0(module.exports = "FIRST-PARTY SOURCE";) inpackages/plain-pkg, committed. A sibling member depends on it viaworkspace:*(Bun lock:"plain-pkg": ["plain-pkg@workspace:packages/plain-pkg"]), or the root depends on it vialink:plain-pkg/file:./packages/plain-pkg. The mock serves a patch for registryplain-pkg@1.0.0. Thenscan --mode agent --json,vex,rollback --yes.Bun layout workspace:*memberlink:file:dir1.4.2 isolated (default for workspaces; member link packages/app2/node_modules/plain-pkg -> ../../plain-pkg)repro (2/2) repro pass (Bun copies it into node_modules, source untouched)1.4.2 linker = "hoisted"repro – – 1.3.9 isolated repro – – 1.2.23 hoisted repro – – 1.1.45 hoisted, v0 text lock repro – – In each repro,
scan --mode agentreportssuccess,applied 2, exit 0. The committedpackages/plain-pkg/index.jsis replaced by the upstream patched file, andgit statusshows it modified.vexgivesnot_affectedforpkg:npm/plain-pkg@1.0.0. Afterrollbackthe file holds the upstream original (module.exports = 'plain-pkg@1.0.0';), so the first-party source is lost. With--json, the envelope has nocontent_mismatch_overwrittenevent (apply.skipped: 0,warnings: null). The warning only appears on stderr in the human output.Hosted mode leaves the member alone (
redirect_bun_entry_not_found, source untouched). Vendored mode refuses it asvendor_lock_entry_not_found(partial_failure, exit 1, source untouched). That's a less specific code than npm'svendor_workspace_member, but it's safe.I haven't checked whether PR #634 covers Bun's layouts (the isolated member link is
packages/<member>/node_modules/<name>, not rootnode_modules). Worth adding a Bun isolated-workspace fixture to its tests.
Generated by Claude Code
- added a commit that references this issue
on Oct 5, 2026
[agent] Found by the scheduled npm bug-hunt routine (ledger #302).
Summary
Agent-mode
scan/applyfollows anode_modules/<name>symlink into first-party source — an npm workspace member, afile:directory dependency, or annpm linktarget — whenever that local package'sname@versionmatches a patched registry package. The localindex.jsdoesn't match the patch'sbeforeHash, so the default mismatch policy overwrites the user's own source with the upstream patched file (only a warning),vexthen attestsnot_affected, androllback"restores" the upstream original file instead of the user's code. The user's source is gone in every case.Vendored mode already recognizes this case and refuses (
vendor_workspace_member: "is a workspace member of this project; patch the source directly instead of vendoring it"), and hosted mode correctly leaves thelink: truelock entry alone (redirect_npm_entry_not_found). Only agent mode writes through the link.Impact
name@versionas upstream — a common way to carry a local fix) has the fork's files silently replaced by upstream content onsocket-patch scan.rollbackdoesn't undo it.npm link left-pad, the link target is the developer's checkout of left-pad in another directory, and agent mode rewrites files there. docs/ecosystems.md uses the same reasoning to skip pnpm's global virtual store ("other projects on the machine load the same files, so patching it in place would patch them as well").Repro (real npm, local mock patch API)
npm linkvariant:cd ~/dev/left-pad && npm link; cd proj && npm link left-pad; socket-patch scan --mode agent→~/dev/left-pad/index.jsis rewritten.Expected vs actual
node_modulesentry that is a link to a workspace member /file:directory /npm linktarget (the lock marks it"link": true) isn't an installed copy of the registry package, so agent mode skips it with a diagnostic, as vendored mode already does (vendor_workspace_member), and never writes through it. The default mismatch overwrite (crates/socket-patch-cli/CLI_CONTRACT.md, "mismatch-policy note";apply.rs:45: "What tolerance can do is discard local modifications to the dependency file") is justified for an installed dependency thatnpm cican restore. It isn't justified for first-party source that no reinstall brings back. docs/ecosystems.md also refuses to patch a store outside the project in place, which is the same hazard as annpm linktarget.Matrix (main
045d7ec, Node 22 / 24)file:dir depnpm link--strictblocks it (exit 1, nothing written). Hosted: no rewrite, loudredirect_npm_entry_not_found. Vendored: refused,vendor_workspace_member.First bad version: not a regression. Released v4.0.0 (
applyagainst the same manifest) overwrites the fork the same way.Suspect code
crates/socket-patch-core/src/crawlers/npm_crawler.rs:1933acceptable_package_entry: importer trees acceptfile_type.is_symlink()for any entry (meant for pnpm/vlt store links, see the doc comment at:1772, which also lists "npm linktargets"), with no check of where the link resolves to: into a store undernode_modules, or into first-party source / outside the project.crates/socket-patch-core/src/vendor/npm_lock.rs:459(vendor_workspace_member).MismatchPolicy::Warn(crates/socket-patch-core/src/patch/apply.rs:51), which overwrites on a mismatch.Probe runs: https://github.com/SocketDev/socket-patch/actions/runs/37081541007 (ubuntu / macOS / Windows × npm 8 / 10 / 12; the first macOS jobs lost a startup race with the mock server, so scan exited 1 on a connect timeout) and https://github.com/SocketDev/socket-patch/actions/runs/37082844719 (macOS rerun with the server warmed: reproduces).