Repository navigation
fix(cli)!: require consent before patching user-patched packages - #1646
Mikola Lysenko (mikolalysenko) wants to merge 7 commits into
Conversation
Detect project patch ownership across pnpm, Yarn, patch-package, npm, Bun, Composer, Cargo and Go. Skip protected scan candidates and refuse explicit mutations unless --allow-user-patches is passed. Preserve native backend refusals and require explicit consent before overwriting unknown installed file contents. BREAKING CHANGE: declared project patches are protected by default and agent apply now rejects unknown file-content drift. Document the next-major-release requirement, detection limits and invocation-only consent in the CLI contract.
The url-spelled crates-io [patch] table and ancestor .cargo/config [patch] fixtures are now caught earlier by the new UserPatchIndex, which refuses with user_patch_conflict before the cargo-specific guards run. Pass --allow-user-patches so these tests keep exercising the guards they were written for (cargo_manifest_patch_source_alias and user_authored_patch_entry); the default refusal is covered by tests/user_patches.rs. Co-Authored-By: Claude <noreply@anthropic.com>
|
Burn-down agent: CI on 98d2edd had two separate failures. Fixed (29dd054): Needs a decision (coverage job): 5 tests in
Question: when the protection comes only from an inspection failure (unparseable lockfile, workspace glob outside the root) and not from real patch evidence, should Generated by Claude Code |
- apply: release apply.lock before reporting a user_patch_conflict, like the manifest-load failures above it, so telemetry can't block siblings. - core: match Cargo [patch]/[replace] names ignoring case and -/_, since crates.io treats those as the same crate. - core: follow go.work `use` members so a member go.mod replace protects its module from the workspace root too. - scan: hide user_patch_conflict/override warnings under --silent, matching user_patches::check; the JSON envelope still carries them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The #480 [patch] override shapes are now caught first by the user-patch gate (user_patch_conflict), so they no longer reach redirect_cargo_dep_overridden. Scan with --allow-user-patches so the shapes keep exercising the redirect guards, and ignore the resulting user_patch_override notice; tests/user_patches.rs covers the gate. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
From /code-review high on #1646: - A `cd` after patch-package (`patch-package && cd ios && pod install`) no longer marks the patch directory unresolvable and blocks all npm. - Yarn alias locators (`alias@npm:real@1.0.0`) protect `real`, the name the crawler reports, instead of a name that never matches. - Cargo [patch] paths use vendor's path_is_socket_owned, so a user path like `.socket/vendor/../../forks/x` is no longer treated as Socket's. - yarn.lock is scanned line by line for patch resolutions instead of parsed (every Berry lock has compat patches); pnpm locks parse with a budget large enough for big monorepos; package-lock/bun.lock are only parsed when they mention `patched`, so a stub or broken lock with no patch evidence no longer protects every npm package. - Workspace globs walk directories only, take only dirs with a package.json as members, flag a symlink only when it would be a member, and resolve `..` lexically (re-rooting `../shared/*`). - Approved user_patch_override warnings print once: check() no longer prints, callers do, and a nested apply leaves them to its caller. - The hosted re-check refuses through the hosted module's refuse(), so the scan envelope is kept and telemetry precedes the JSON error. - A scan whose every package is protected says so instead of "No packages found. Run your package manager's install first." - Go hosted/build tests for replace guards now pass --allow-user-patches so they keep testing the guards behind the consent gate. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Two review findings on #1646, resolved as decided with the maintainer: - apply no longer refuses a whole run over one protected manifest entry (a postinstall apply would fail every install). Protected records sit the run out with a user_patch_conflict warning, everything else applies, and skips alone exit 0. The manifest on disk is unchanged, and skipped Go entries are kept out of the stale-redirect reconcile. `apply --check` stays read-only and verifies every entry. Explicit `get` and `vendor` still refuse; vendor now reverts dropped entries before its check so a protected record can't block that cleanup. - The default mismatch policy is the new MismatchPolicy::SocketContent: Strict, except a file whose hash names a stored .socket/blobs blob (another Socket patch's output, as after an update to a newer patch for the same release) is overwritten with the usual content_mismatch_overwritten warning. --strict stays fully strict. Contract and usage docs updated to match. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- A nested apply under `get` now reports the older manifest entries it skips for project patches (stderr and the JSON envelope via the run report); `get` checks only its own selection. `scan` already reports every protected installed package, so its nested apply stays quiet. - vendor: a conflict found after reconcile_dropped already wrote is recorded as per-package `failed` events instead of a top-level error, which the contract reserves for runs where nothing happened. - patch-package `cd` detection matches `cd` as a command, not the tail of a word such as `abcd`. - The ancestor workspace walk never reaches the home directory or above, so a checkout without `.git` doesn't read `~/.cargo/config.toml` or a stray `~/patches/` as project evidence. - Discovery is skipped when no in-scope purl is in an ecosystem the detector reads (an empty or pypi-only manifest on postinstall). - npm/Bun locks are read once instead of twice. - The scan message for protected packages no longer claims they were the only reason the scan came up empty. - Contract: a pruned or unshared blob means earlier Socket output reads as drift; `apply --force` is the explicit way through. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
bugbot run Generated by Claude Code |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
Bugbot Autofix is ON, but none of the reported issues met your autofix severity threshold.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 6cb2793. Configure here.
| nested: Some(super::apply::NestedApply { | ||
| caller_json: json, | ||
| report_user_patch_skips, | ||
| }), |
There was a problem hiding this comment.
Skipped apply still reported applied
Medium Severity
Nested apply now drops a protected manifest entry and still exits 0. Agent scan/get then record every saved selection as applied. Hosted rechecks under the write lock for this window; agent no longer fails the run, and scan also suppresses the skip warning, so a package added to project patch metadata after selection can be saved and reported patched without a write.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 6cb2793. Configure here.
There was a problem hiding this comment.
Traced it. In get/scan, the selection already runs the same user-patch detector (with the same allow_user_patches) before anything is recorded. So a recorded purl is skipped by the nested apply's skip_protected only if the project's patch metadata (a pnpm/Yarn/patch-package/Composer/Cargo patch entry) is added between selection and the nested apply in the same run. In that window, record_apply_outcome sees code == 0 and records applied for a package that was left unwritten. The race is real but narrow. Fixing it means adding a user_patched list to ApplyRunReport and mapping covered purls to skipped in record_apply_outcome, about 20 lines plus a test. I'm leaving this thread open for the maintainer to decide whether that's worth adding to this PR.
Generated by Claude Code
|
Burn-down agent: CI is green on Question for a human: should this PR fix that race now, by mapping purls that apply skipped as user-patched to Generated by Claude Code |


A project could already have a pnpm, Yarn or patch-package fix and still receive a Socket patch without specific consent. Native npm/Bun had backend-specific refusals, but there was no shared package-ownership check. Agent apply also overwrote unknown installed file contents with the verified Socket blob by default, potentially discarding a local fix.
This change protects project patches before patch selection or mutation, even when the two fixes touch different files or the user diff has not been installed yet.
user_patch_conflictwarnings in every mode.apply(including nested apply) leaves protected manifest entries out with the same warning and applies the rest, so a postinstall apply never fails an install over them. Explicitgetandvendorrefuse conflicts with exit 1;vendorreverts dropped entries first. Recheck ownership before hosted/vendored writes, including hosted-to-vendored eject, and retain the full crawl for pruning.--allow-user-patches, forwarded through nested apply and reported asuser_patch_override. General--yes, UUID selection, policy settings, and--forcedo not approve declared patch composition. Existing backend safety refusals remain in force..socket/blobs/blob, as after a patch update), which is overwritten with the usual warning. Explicit consent restores the previous hash-verified overwrite behavior;--strictkeeps rejecting drift even with consent. Existingapply --forceremains an explicit override for file drift only. Consent is an attempt to combine changes, not an automatic merge.The CLI contract and usage docs specify diagnostics, read-only/reversal exemptions, version matching, workspace behavior, and detection limits. The ecosystem documentation links the primary package-manager documentation and source used for the research. Regression coverage includes pnpm wildcard prereleases, nested Yarn patch locators in both directions, scoped/nested/sequenced patch-package names, malformed metadata, all CLI modes, nested opt-in forwarding, and preservation of user files.
Breaking change / release coordination: the stricter mismatch default and package eligibility belong in the next major release under the CLI semver policy. Version stamps and release notes remain the release train's responsibility.
Detection is conservative where metadata is unreadable or cannot be resolved without executing scripts. Arbitrary build hooks, external patch pipelines, global-install declarations, and unrecorded edits to files a Socket patch does not touch cannot be detected reliably. These limits are explicit in the contract. Scan exits 0 when a package is protected only because metadata could not be inspected; the warning names the file.
Validation passed locally:
cargo check --locked -p socket-patch-cli --testscargo clippy --locked --workspace --all-features -- -D warningscargo fmt --all -- --checkandgit diff --checkpatch/patch-commitworkflow with a local Socket test record targeting a different file: default apply and--forcerefused;--allow-user-patchesapplied the Socket fix while preserving the native diff and user-modified file./code-review highran on 4b3d620; all 10 findings were fixed (the drift-default, apply-scope and scan-exit questions were decided with the maintainer as described above). A second/code-review highpass on e48ded2 found 10 more candidates; 7 are fixed in 6cb2793 and one is documented. The other three:cdin scripts,**globs, big lockfiles) are fixed.apply --forcereplaces it.Note
High Risk
Major behavior change to default apply/scan eligibility and file-overwrite policy on dependency trees, with new consent gates across mutating commands that can block or alter installs in projects using native patching workflows.
Overview
Breaking (major): Socket Patch now treats declared project patches as protected ownership and tightens in-place apply safety unless the user opts in per invocation.
Adds global
--allow-user-patches(not env-bound;--yes,--force, and UUID selection do not substitute). A new detector scans manifests/locks for pnpm, Yarn,patch-package, npm/Bun, Composer, Cargo, and Go patch/replace evidence.scanwithholds protected packages in every mode withuser_patch_conflictwarnings;applyskips those manifest entries but still patches others (postinstall-friendly exit 0);get/vendor/ hosted & vendored writers refuse withuser_patch_conflictbefore mutation (with a lock-time recheck on hosted writes). Consent emitsuser_patch_overrideand is forwarded through nested apply.File mismatch policy flips: unknown local drift on apply paths fails by default instead of warn-and-overwrite. The exception is content whose hash matches a stored
.socket/blobs/entry (prior Socket patch output on update), which can still be overwritten withcontent_mismatch_overwritten.--allow-user-patchesrestores hash-verified overwrite for other drift;--strictrejects drift even with consent;apply --forcestill overrides file-content checks but not declared project patches. Core addsMismatchPolicy::SocketContentto implement this.Docs (
CLI_CONTRACT, usage, ecosystems) and broad CLI/core tests cover the new gates, diagnostics, and policy behavior.Reviewed by Cursor Bugbot for commit 6cb2793. Configure here.
Generated by Claude Code