[agent] Found by the scheduled npm bug-hunt routine (ledger #302). The Bun routine first reported it as a handover (Bun nested projects); I've reproduced it with real npm projects.
Summary
Run socket-patch scan --mode agent at a repo root that holds independent nested npm projects, each with its own package-lock.json and node_modules. The crawler patches the installed copies in every nested project. The socket.yml path policy is only evaluated once, for the scan root, so patches.ignorePaths, patches.includePaths, projectIgnorePaths and the built-in test/ tests/ fixtures/ … defaults never exclude a nested project. The JSON policy block reports counts: {filtered: 0, retained: 0}, so nothing tells the user that the policy wasn't applied.
Hosted and vendored modes honour the same file. scan '*/*' --mode hosted skips services/legacy with /services/ (patches.ignorePaths).
Impact
- In agent mode, a repo can't use path policy to keep socket-patch out of a legacy service, an example, or a test fixture project. Those projects'
node_modules get rewritten in place anyway. The docs' own headline example (includePaths: ["apps/**", "services/**"], ignorePaths: ["services/legacy/**"]) does nothing in agent mode.
includePaths: ["/*", "!/*/"], documented as "targets only the repo-root project", still patches every nested project.
- Default-ignored fixture projects under
tests/ get patched, which can break fixture tests that pin the upstream bytes.
- Agent PATH globs (
scan 'services/**' --mode agent) also report filtered: 0. Policy is never consulted per copy.
Repro (Linux, main 61cfb9b, npm 10.9.4 / Node 22 and npm 12.2.0 / Node 24.21; reproduced 3× on npm 10 and 1× on npm 12)
The patch API is a local mock (the one described in ledger #302) serving patches for left-pad@1.3.0, is-number@6.0.0 and is-number@7.0.0. Any patch source works.
mkdir repo && cd repo && git init -q
mk(){ mkdir -p $1; (cd $1; echo "{\"name\":\"$(basename $1)\",\"version\":\"1.0.0\",\"private\":true,\"dependencies\":{$2}}" > package.json; npm install --no-audit --no-fund); }
mk . '"left-pad":"1.3.0"'
mk apps/web '"is-number":"6.0.0"'
mk services/legacy '"left-pad":"1.3.0","is-number":"7.0.0"'
mk tests/e2e '"is-number":"6.0.0"'
printf 'version: 2\npatches:\n ignorePaths: ["/services/"]\n' > socket.yml
socket-patch scan --mode agent --yes --json > scan.json # exit 0
jq -c .policy.counts scan.json # {"filtered":0,"retained":0}
head -c 21 services/legacy/node_modules/left-pad/index.js # /* SOCKET-PATCHED */ <- should be untouched
head -c 21 tests/e2e/node_modules/is-number/index.js # /* SOCKET-PATCHED */ <- default-ignored dir
With includePaths: ["/*", "!/*/"] instead, all five copies are patched too (the root, apps/web, both services/legacy copies and tests/e2e).
Control, using the same tree and the same socket.yml:
socket-patch scan '*/*' --mode hosted
== services/legacy ==
Policy (socket.yml): 1 skipped by filters, 0 patched packages held.
skipped project services/legacy: /services/ (patches.ignorePaths)
# apps/web lock is rewired; services/legacy and tests/e2e locks untouched
Expected vs actual
- CLI_CONTRACT.md, "socket.yml patch policy" → Paths: path lists "are matched against a project root's marker files … the lockfiles in the root's directory". A root is ignored iff every marker is ignored, and
includePaths: ["/*", "!/*/"] "targets only the repo-root project". The built-in defaults "apply only to discovered roots". docs/configuration.md says includePaths / ignorePaths "include or exclude project roots by their dependency-file paths".
services/legacy/package-lock.json is a project root marker that matches /services/. Copies installed under that root should be filtered (and reported in policy.filtered[]), or at minimum the agent run should warn that path policy doesn't apply to nested roots.
- Actual: every nested project is patched, with
filtered: 0 and exit 0.
Matrix (Linux)
| npm / Node |
ignorePaths: ["/services/"] |
includePaths: ["/*","!/*/"] |
tests/ default |
hosted '*/*' control |
| 10.9.4 / 22.22 |
fail (3/3) |
fail |
fail |
pass |
| 12.2.0 / 24.21 |
fail (1/1) |
not run |
fail |
not run |
macOS and Windows weren't probed. The defect is in the OS-independent policy wiring. Bun projects reproduce it too (Bun routine handover, ledger #306), so it applies to the whole npm-family crawler.
First bad release: none. socket.yml patch policy is new in v5 (#293); v4.0.0 has no policy.
Suspect code
crates/socket-patch-cli/src/commands/scan/mod.rs:1472: a non-hosted/vendored scan builds one ScanPolicy::for_root(invocation, &args.common.cwd, …) for the whole run.
crates/socket-patch-cli/src/commands/scan/policy.rs:141: for_root evaluates admits_root once, for the cwd ("disk scans run one root per run_scan"). The agent crawl, though, descends into nested projects' node_modules, so their copies are never judged against their own root's markers.
[agent] Found by the scheduled npm bug-hunt routine (ledger #302). The Bun routine first reported it as a handover (Bun nested projects); I've reproduced it with real npm projects.
Summary
Run
socket-patch scan --mode agentat a repo root that holds independent nested npm projects, each with its ownpackage-lock.jsonandnode_modules. The crawler patches the installed copies in every nested project. Thesocket.ymlpath policy is only evaluated once, for the scan root, sopatches.ignorePaths,patches.includePaths,projectIgnorePathsand the built-intest/ tests/ fixtures/ …defaults never exclude a nested project. The JSONpolicyblock reportscounts: {filtered: 0, retained: 0}, so nothing tells the user that the policy wasn't applied.Hosted and vendored modes honour the same file.
scan '*/*' --mode hostedskipsservices/legacywith/services/ (patches.ignorePaths).Impact
node_modulesget rewritten in place anyway. The docs' own headline example (includePaths: ["apps/**", "services/**"],ignorePaths: ["services/legacy/**"]) does nothing in agent mode.includePaths: ["/*", "!/*/"], documented as "targets only the repo-root project", still patches every nested project.tests/get patched, which can break fixture tests that pin the upstream bytes.scan 'services/**' --mode agent) also reportfiltered: 0. Policy is never consulted per copy.Repro (Linux, main
61cfb9b, npm 10.9.4 / Node 22 and npm 12.2.0 / Node 24.21; reproduced 3× on npm 10 and 1× on npm 12)The patch API is a local mock (the one described in ledger #302) serving patches for
left-pad@1.3.0,is-number@6.0.0andis-number@7.0.0. Any patch source works.With
includePaths: ["/*", "!/*/"]instead, all five copies are patched too (the root,apps/web, bothservices/legacycopies andtests/e2e).Control, using the same tree and the same
socket.yml:Expected vs actual
includePaths: ["/*", "!/*/"]"targets only the repo-root project". The built-in defaults "apply only to discovered roots". docs/configuration.md saysincludePaths/ignorePaths"include or exclude project roots by their dependency-file paths".services/legacy/package-lock.jsonis a project root marker that matches/services/. Copies installed under that root should be filtered (and reported inpolicy.filtered[]), or at minimum the agent run should warn that path policy doesn't apply to nested roots.filtered: 0and exit 0.Matrix (Linux)
ignorePaths: ["/services/"]includePaths: ["/*","!/*/"]tests/default'*/*'controlmacOS and Windows weren't probed. The defect is in the OS-independent policy wiring. Bun projects reproduce it too (Bun routine handover, ledger #306), so it applies to the whole npm-family crawler.
First bad release: none. socket.yml patch policy is new in v5 (#293); v4.0.0 has no policy.
Suspect code
crates/socket-patch-cli/src/commands/scan/mod.rs:1472: a non-hosted/vendored scan builds oneScanPolicy::for_root(invocation, &args.common.cwd, …)for the whole run.crates/socket-patch-cli/src/commands/scan/policy.rs:141:for_rootevaluatesadmits_rootonce, for the cwd ("disk scans run one root perrun_scan"). The agent crawl, though, descends into nested projects'node_modules, so their copies are never judged against their own root's markers.