v5: socket.yml patch rollout config and filtering - #293
Mikola Lysenko (mikolalysenko) merged 24 commits into
Conversation
Adds the design for rolling Socket patches out gradually: a `patches:` block in socket.yml that narrows what scan may patch (paths, ecosystems, packages, severity floor, on/off), and a severity-ordered per-run cap on new patches so each scan lands the next few most critical fixes. The plan splits the work into two parallel items with a frozen interface, lists every hard-coded filter and where it belongs, and covers the depscan autopatch follow-up. configuration.md now records that socket-patch reads socket.yml for selection policy only, with the trust boundary unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Three independent reviews (ambiguity, churn, trust boundary) found gaps that would have let the two implementations disagree or let a repo file widen or stall the rollout. The plan now: - matches paths against marker files with the backend's top-down gitignore rules, so projectIgnorePaths means the same everywhere - uses one data source for severity, supersession and ordering - spends the budget only on patches the planning pass proves can land, and admits nothing new when a lookup failed - uses the merged recorded view in every mode and engine - has depscan read the policy from the base commit for PR jobs - hardens file handling (regular files, aliases, size, encoding, trusted repo root) and reports what a policy hides Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A final consistency pass found places where the two work items would have produced incompatible code: base purls admitted in one directory being charged again in the next, no defined hand-off of the unfiltered offers from the severity filter to classification, no shared repo-relative path helper, and override sources the JSON must report but the interface could not carry. The shared contract now defines each of these, and the parity tests match each engine's budget scope. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The plan said both that the selector returns the shared offers struct (work item A) and that it returns admitted/deferred rows (work item B). The selector now returns the offers, and B adds the rollout stage after it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…taged-rollout-plan
New socket_patch_core::policy module: the SelectionPolicy, Offers and repo-root helpers that the staged-rollout plan freezes as the shared contract between the socket.yml work and the --max-new-patches work. It reads the repo root's socket.yml/socket.yaml strictly and fails closed: bad YAML, a misspelled `patches` block, unknown keys (with a did-you-mean hint), wrong types, empty allowlists, bad globs and anchors or aliases inside the keys we read are all errors that name the key path. Path lists match marker files with npm `ignore` semantics, walked top-down; a golden fixture generated from the npm package pins that. The built-in test/fixture ignores become overridable defaults for discovered roots. --package matching moves to core so scan and the policy share it. Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
scan now reads the repo root's socket.yml before any write and applies its patches block in hosted, vendored and agent mode, --dry-run included: path filters on project roots (PATH-glob matches also get the built-in test/fixture ignores), ecosystem and package filters on crawled packages, and a severity floor on the patches a package may receive. An invalid or ambiguous file fails the run with exit 1 and an errorCode before any request. Packages that already carry a patch are never removed, upgraded or replaced by the policy: they are held and reported under policy.retained. A recorded patch below a new floor stays in place. patches.enabled: false reports what would be patched and writes nothing. New flags: --no-socket-yml / SOCKET_NO_SOCKET_YML and --min-severity / SOCKET_MIN_SEVERITY. Every successful scan --json result gains a top-level policy block. A PATH outside the repository root is a usage error. Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
selectHostedScanPaths now streams the root socket.yml/socket.yaml and returns them as policyPaths; it drops test and fixture trees through the policy's built-in default ignores instead of a hard-coded segment list (structural excludes like node_modules and vendor stay fixed). The session reads the policy before any root is processed: path filters run before the project limit, ecosystem and package filters on each root's packages, and the severity floor before selection. A listed policy file that arrives without content, or an invalid one, yields policyError with no root processed and no file changed. New options noSocketYml, minSeverity and policyPaths; the result gains a policy block. hosted-bundle and index.d.ts carry the new fields. get now warns policy_bypassed when the repo's socket.yml would have skipped the package it patches. Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
CLI_CONTRACT.md gains a "socket.yml patch policy" section (grammar, precedence, paths, lookup, validation, commands, error codes and the policy JSON block), the flag and env rows, and the "narrow or pace" half of the trust-boundary rule. README adds a "Roll out gradually" section with copyable socket.yml recipes, CHANGELOG lists the new policy and the breaking scan changes, and the design docs record the decisions made while building it. Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Fixes from an adversarial self-review of the policy work: - A non-ASCII package spec no longer panics validation, and error text, warnings and verbose lines drop terminal escapes and bidi or zero-width characters. - Ignore lists that fail to compile together are an error instead of silently matching nothing, and the policy file is opened by its resolved path without following a swapped-in symlink. - A top-level key that looks like a misspelled `patches` (`patchs`), a top-level merge key or an aliased key now fails closed. - Repo-root lookup follows a `.git` symlink and trusts the checkout owner under root and sudo, so CI containers keep the policy. - The severity floor always reports the patch it held back, report- only --json runs report what a floor or `enabled: false` hides, a root skipped as a whole is always one entry and no longer prints "No packages found", warnings print once per invocation, and the policy line is omitted when there is nothing to say. - policy entries are sorted and use canonical purls on disk and in memory; the in-memory engine applies a socket.yml negation of a built-in ignore to roots it was given, and policyError carries no CLI-only remedy. - A lockfile-less disk root matches path filters by its manifests. Tests cover each fix, plus the prune universe, agent path filters, a vendored package held byte-identical, and tighter oracles; docs are corrected where they overstated what the human output names. Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
[agent] CI note:
In each, This is not caused by this PR. I don't know of a fix to port yet. I'll re-run the failed jobs once when this run finishes. Generated by Claude Code |
|
[agent] CI note: Which cases fail. Only the hosted cases fail:
The socket-patch side of those cases is fine: the relock keeps the patch, Why I don't think this PR causes it.
I can't pull the capture artifact from this sandbox, so I can't prove it further. The re-run is used; I'll keep watching and look again if it shows up on the next push. Generated by Claude Code |
|
[agent] This failure comes from the base branch:
No fix exists yet, so I haven't ported one. The job logs cut off before the failing test's name, so I can't propose a patch from here. I won't rerun this job, because the same failure on the base branch already rules out a flake. Generated by Claude Code |
The optimized test job now has to build two more crates and one more test binary for the socket.yml policy. It ran out of time on its last 40 minutes, about a minute short, and a docs-only change already takes 38. Raise the limit to 50 minutes so it can finish. Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Brings in the merged staged-rollout plan (#290), the Windows scan test fix (#288) and the docs-only design PRs. The plan's late two-phase memory selection is implemented in the next commit. Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The merged plan makes in-memory path selection two-phase: the host fetches the root socket.yml first and passes its text to selectHostedScanPaths. Selection now applies the full path policy, so a negation such as `!/e2e/tests/` brings a test tree back in memory exactly as on disk. A listed policy file that is missing, symlinked or invalid returns policyError with nothing selected. Selection returns policySha256, and the session fails closed when the policy it reads differs. Roots the policy excludes keep their marker files presence-only, so the session still lists them as filtered. Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Review follow-ups to two-phase selection: - Roots the policy excludes are reported in ignoredSample instead of streamed presence-only, so a repo with many fixture lockfiles no longer runs into the session's file limit. - A session that bypasses socket.yml while selection applied it now fails closed instead of processing roots it never fetched. - The docs say policy text must decode losslessly (TextDecoder drops a BOM) and when policySha256 is null. Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
[agent] Merged What changed
Gap decisions (recorded in staged-rollout.md §9.4, item 9)
Other changes
Checks run locally: clippy ( Generated by Claude Code |
Brings in 28cebf7, which fixes the hosted rollback failures the vlt compatibility legs hit on this branch. Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
[agent] Update on the vlt compatibility failures, now at
Generated by Claude Code |
|
[agent] The
Generated by Claude Code |
|
#281 landed on release/v5-prerelease as 73c0c4f; please merge origin/release/v5-prerelease again, resolve conflicts, get green, and keep it ready. Generated by Claude Code |
Brings in #281 (one lockfile model per ecosystem). No conflicts; clippy and the policy, memory, parity and scan suites pass. Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
#279 landed on release/v5-prerelease as f6bdad5; please merge origin/release/v5-prerelease again, resolve conflicts, get green, and keep it ready. Generated by Claude Code |
Brings in #279 (setup removed, patch UI streamlined). Conflicts: - core lib.rs keeps `policy` and drops `setup`. - scan keeps policy-aware selection (`select_accessible`) and the base's `selection_args`, which `get` now uses. - CLI_CONTRACT exit-code rows take the base's text plus the socket.yml rows. Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The v5 help rules cap each command's short help at about eight options. `--no-socket-yml` and `--min-severity` pushed `scan -h` to ten, so they now appear only in `scan --help`, like the other advanced scan flags. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3
|
[agent] ready to land Head
Generated by Claude Code |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issues.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit f680990. Configure here.
A report-only `scan --json` (`--prune` with no mode) with a severity floor or `enabled: false` fetches patch details to fill `policy.filtered[]`. If every query failed it still printed a success envelope and exited 0. It now reports the error and exits 1, like the agent and vendored runs. Human-mode policy warnings now print `Warning: <detail>` like the rest of `scan`; the code stays in the JSON `warnings[]` entry. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3
|
#282 landed on release/v5-prerelease as 14a9cb0; please merge origin/release/v5-prerelease again, resolve conflicts, get green, and keep it ready. Generated by Claude Code |
Brings in #282, which moves the in-memory engine into socket-patch-core (`hosted::memory`). The two-phase policy selection moves with it. Conflicts: - `canon`, `FilteredEntry`, `RetainedEntry` and `policy_block` move from the CLI into `socket_patch_core::policy`, so the core engine and disk scans share one `policy` block. - `roots` is now public in core, for disk scans' marker lookup. - scan reads the ledgers and wiring through the base's `ProjectContext`, and keeps loading the recorded view before filtering. - CLI_CONTRACT's hosted-bundle row takes the base's path, plus the policy fields. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3
|
[agent] ready to land Head What the merge changed. #282 moved the in-memory engine into
This head also includes CI on
Generated by Claude Code |
|
#291 landed on release/v5-prerelease as f9cb7e1; please merge origin/release/v5-prerelease again, resolve conflicts, get green, and keep it ready. Generated by Claude Code |
Brings in #291, which builds the e2e test binaries once and tiers the package-manager matrix. The only conflict is the `test-release` timeout: it takes the base's 40 minutes. The 50 minutes here was sized for the two feature-set builds that #291 removes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3
|
[agent] ready to land Head
Generated by Claude Code |
|
#292 landed on release/v5-prerelease as a7b0d00; please merge origin/release/v5-prerelease again, resolve conflicts, get green, and keep it ready. Generated by Claude Code |
Brings in #292, which fixes the partial-stage repair bug and cuts redundant downloads. No conflicts; clippy and the policy, in-memory, parity, e2e policy, parser and help suites pass. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3
|
[agent] ready to land Head
Generated by Claude Code |
|
#296 landed on release/v5-prerelease as 1e3ace6; please merge origin/release/v5-prerelease again, resolve conflicts, get green, and keep it ready. Generated by Claude Code |
Brings in #296, which removes dead code and the v3 compatibility shims. The only conflict is CLI_CONTRACT's exit-code rows: they take the base's text (no `--detached`, `--one-off` removed) plus the socket.yml and SOCKET_MIN_SEVERITY rows. Clippy and the policy, in-memory, parity, e2e policy, parser, help and scan suites pass. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3
|
[agent] Generated by Claude Code |
|
[agent] ready to land Head d9dd0ff merges #296 (base 1e3ace6) with conflicts resolved. CI has finished on this head:
Generated by Claude Code |
|
#297 landed on release/v5-prerelease as b97a1c2; please merge origin/release/v5-prerelease again, resolve conflicts, get green, and keep it ready. Generated by Claude Code |
Brings in #297, which groups the CLI tests into one binary per command and replaces the #257 oracles with golden files. It merges cleanly; this PR's test binaries stay at the top level, as most still do. Clippy and the policy, in-memory, parity, e2e policy, parser, help, scan and get suites pass. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3
|
[agent] ready to land Head 29674bb merges #297 (base b97a1c2) with no conflicts. CI has finished on this head:
Generated by Claude Code |
b9e106d
into
release/v5-prerelease
Brings in #293, the socket.yml rollout config this branch builds on. This branch already carries every change #293 made, so every conflict resolves to this branch's side and the tree matches the previous head exactly. No code changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AvGSu5jGg1z3f3sePc2eHC

Work item A of the v5 staged-rollout design (#290,
docs/design/staged-rollout.md§9.1).scanreads the repo root'ssocket.ymland uses it to narrow what it patches. This covers hosted, vendored and agent mode (--dry-runincluded) and the in-memory engine behind the autopatch bot. The per-run cap (--max-new-patches, work item B) builds on the shared types this PR adds.What users get
A
patchesblock in the rootsocket.yml. Keepversion: 2so the scanner and socket-cli keep working; they strip or ignore the block.enabled,includePaths,ignorePaths,ecosystems,packages,ignorePackages,minSeverity(critical|high|medium|moderate|low) andmaxNewPatches.maxNewPatchesis validated here and enforced by B.ignoresemantics, matched against each project's lockfiles. A golden fixture generated from the npm package pins this.projectIgnorePathsis now honored too.test/ tests/ fixtures/ __fixtures__/ testdata/are skipped by default for discovered roots (scan 'services/*', in-memory detection). This replaces the hard-coded list in the in-memory engine. Re-include one withignorePaths: ["!/e2e/tests/"], on disk and in memory. A directory you name yourself is always scanned.--min-severity <level|none>/SOCKET_MIN_SEVERITYbeats the file's floor. New--no-socket-yml/SOCKET_NO_SOCKET_YMLignores the file.policy.retained[]withupgradeAvailable. A recorded merged patch below a new floor is kept.scanbefore any request or write: exit 1,errorCode: socket_yml_invalid(orsocket_yml_ambiguouswhensocket.ymlandsocket.yamldisagree). The message names the key path, gives a did-you-mean hint, and suggests--no-socket-yml. Example:socket.yml: patches.minSeverty: unknown key (did you mean minSeverity?) ….scan --jsongains a top-levelpolicyblock:Policy (socket.yml): N skipped by filters, M patched packages held.and always names skipped critical/high patches.getignores the policy and warnspolicy_bypassed. A hosted/vendored PATH outside the repo root is exit 2.--globalreads no file.socket.yml/socket.yamlfirst and passes it toselectHostedScanPathsaspolicyFiles: [{path, text} | {path, missing: true}], plusnoSocketYml.policyPaths,policySha256andpolicyError. An excluded root goes intoignoredSamplewith itspolicy_*reason and is not streamed.noSocketYml,minSeverity,policyPathsandpolicySha256. It failssocket_yml_invalidif the policy it reads differs from what selection read.policy, orpolicyErrorwith no root processed and no file changed.hosted-bundleandindex.d.tshave the new fields.Trust boundary (CLI_CONTRACT.md): socket.yml may narrow or pace, never widen. Keys like
apiUrl,apiToken,org,modeordownloadModeare unknown keys and fail validation.Layout
crates/socket-patch-core/src/policy/holds the frozen §9.0 contract:SelectionPolicy,PolicySource,FilterReason,PolicyError,PolicyFs,Root,Offers;package_spec_matches, moved from the CLI;find_repo_root,repo_relative.It also holds the strict parser (
socket_yml.rs) and the top-down gitignore matcher (paths.rs).commands/scan/policy.rs,commands/scan/socket_yml_args.rs: disk glue and flags.discover_selectednow returnsOffers.hosted_memory/select.rs: two-phase policy selection.New deps, exact-pinned:
serde-saphyr =1.3.0andignore =0.4.33.CI: the
test-releasetimeout goes from 40 to 50 minutes. A docs-only change already takes about 38 minutes, and this PR adds two crates and a test binary.Decisions where the plan left a gap
These are recorded in
docs/design/staged-rollout.md§9.4:patches/projectIgnorePaths. serde_norway can't refuse them per subtree.search_result_supersedes.enabled: false: recorded packages go toretained[], the rest tofiltered[].retained[]yet, andpolicy.filtered[]lists only the roots the session received.policySha256, returned by selection and passed to the session; a session that reads a policy file without it fails closed.policyFiles.textmust be a lossless decode (Nodebuffer.toString('utf8');TextDecoderdrops a BOM).Tests
.gitdir, file or none,GIT_CEILING_DIRECTORIESand a foreign owner;ignoregolden fixture, and this repo's own socket.yml.tests/e2e_socket_yml_policy.rs, real binary, wiremock catalog, monorepo with npm roots and a gem):includePaths,projectIgnorePaths;--no-socket-yml;enabled: false, PATH outside the repo, andget's warning.hosted_memory_parity.rs, through the real two-phase flow):policyError;cli_parse_scan.rsrows for both flags and env vars.cargo clippy --workspace --all-features --all-targets -D warningsis clean. The policy, in-memory, parity, scan and get suites pass locally.CI failures inherited from the base (not this PR's)
28cebf7fails the same way.yarn-classic1.0.2 / 1.6.0 / 1.7.0 / 1.9.4:mode_migration_npmexpects anintegrityline that old yarn 1.x doesn't write. ci: pin TLS-verified patch hosts on macOS compat legs (fix hosted DNS flake) #295 fails the same legs.🤖 Generated with Claude Code
Generated by Claude Code
Note
Medium Risk
Changes what
scanpatches in all modes and CI, but policy only narrows behavior with fail-closed validation and extensive e2e/parity tests.Overview
scannow honors repo-rootsocket.ymlto narrow patching across hosted, vendored, agent, dry-run, and the in-memory autopatch engine. Apatchesblock (plus existingprojectIgnorePaths) can disable writes, scope by gitignore-style paths/ecosystems/packages, and set a severity floor; existing recorded patches are never removed—filtered packages land inpolicy.retained[]with reporting via a new top-levelpolicyJSON block and humanPolicy (socket.yml): …line.CLI:
--min-severity/SOCKET_MIN_SEVERITYand--no-socket-yml/SOCKET_NO_SOCKET_YML; invalid or ambiguous YAML failsscanbefore network or disk writes (exit 1,socket_yml_invalid/socket_yml_ambiguous). Discovered roots skiptest/,tests/,fixtures/, etc. by default (negation supported);getbypasses policy withpolicy_bypassedwarnings. Policy logic lives in newsocket-patch-corepolicy(serde-saphyr,ignore); in-memory flow is two-phase (selectHostedScanPaths→ session withpolicySha256). CItest-releasetimeout raised 40→50 minutes.Reviewed by Cursor Bugbot for commit f680990. Configure here.