feat: three patch-apply modes — hosted, vendored, agent — with full ecosystem + VEX coverage#117
Conversation
Adds a new patch-apply mode that rewrites lockfiles/manifests so ONLY patched dependencies resolve from Socket's hosted vendored patches (patch.socket.dev), instead of vendoring local artifact bytes or writing .socket/manifest.json. - `scan --redirect` flag (conflicts_with_all apply/sync/vendor) - `--patch-server-url` / `SOCKET_PATCH_SERVER_URL` global arg, defaulting to https://patch.socket.dev (DEFAULT_PATCH_SERVER_URL) - api/client.rs `fetch_registry_references` (authed /v0/orgs/{org}/patches/package, or proxy /patch/package) - patch/redirect/mod.rs rewriters for 9 ecosystems (npm package-lock, pnpm, yarn-classic, pypi requirements, uv, cargo, composer, nuget, gem); golang documented as a limitation (no per-dependency remote redirect without a global GOPROXY) Shared golden fixtures (tests/fixtures/redirect/**) are consumed by BOTH this crate's redirect_golden.rs and the depscan backend's golden.test.ts, keeping the two rewriter implementations byte-identical. Behavioral coverage: tests/in_process_redirect.rs. Assisted-by: Claude Code:opus-4-8
|
All alerts resolved. Learn more about Socket for GitHub. This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored. |
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
…arker `scan --redirect` previously dropped `--vex` silently and left no trail VEX could attest: the redirect ledger carried only file edits, no patch records, and the VEX verifier never read it. - redirect ledger (`.socket/vendor/redirect-state.json`) now embeds the manifest PatchRecord (file hashes + vulnerabilities) per redirected PURL, fetched from the view endpoint during the redirect run; schema lives in patch/redirect/state.rs (RedirectState, load_redirect_state) - vex: new augment_with_redirect folds ledger records into the manifest view (mirror of augment_with_detached) for both the standalone `vex` command and the embedded --vex paths, so redirected patches attest post-install against the installed tree exactly like `apply` - build: build_document_with_provenance generalizes the vendored marker with a `(redirected)` impact phrasing; redirected PURLs bypass the property-7 ecosystem filter for the same reason vendored ones do (the committed lockfile rewrite is the persistence mechanism) - scan --redirect --vex now emits the OpenVEX document in-run (the redirected bytes are remote pre-install, so the in-run attestation is built from the ledger records; a post-install `socket-patch vex` hash-verifies the installed tree) Tests: e2e_vex_redirect.rs (installed-tree verify, property-7 bypass, tampered→hash_mismatch fail-closed, no-verify ledger attestation, cross-ecosystem incl. maven/nuget/composer + qualified PURLs), in_process_redirect.rs --vex leg, core unit tests for the ledger round-trip and the (redirected) phrasing, and real-toolchain VEX legs in the npm/cargo/pnpm/yarn-classic/pypi/golang e2e_vendor_*_build capstones. Assisted-by: Claude Code:claude-fable-5
Pure rustfmt reflows (rewraps, trailing commas, brace elision) — no logic changes. Normalizes the #116 merge and this branch's test files to plain `cargo fmt` output. Assisted-by: Claude Code:claude-fable-5
…EX legs e2e_redirect_npm_build.rs proves the full redirect chain with the real toolchain: a mock patch server serves real patched tarball bytes; scan --redirect rewrites package-lock.json; a fresh-checkout npm ci (empty cache) installs the patched bytes from the served URL; a post-install socket-patch vex HASH-VERIFIES the installed tree and emits the (redirected) statement. The negative leg serves tampered bytes against the real lockfile pin and asserts npm ci fails with EINTEGRITY — the pin is enforcement, not decoration. The docker vendor suites (gem via bundler, composer) gain a VEX leg inside the existing stage-1 container run: the staged manifest now carries a vulnerability, socket-patch vex runs in-container against the vendored artifact, and a host-side oracle asserts the document (one statement, not_affected, correct PURL subcomponent, "(vendored)" marker). Both suites executed against real Docker containers. Assisted-by: Claude Code:claude-fable-5
…faced record-fetch failures Adversarial-review findings on the redirect->VEX wiring, each with a pinned regression test: - CRITICAL: the in-run --vex exemption trusted the whole redirect ledger, so a granted patch whose rewriter never touched a file (no lockfile present), a stale ledger record, or a manifest patch that failed verification could all be attested not_affected — suppressing a live CVE. run_redirect now derives a CONFIRMED set (the dep's hosted URL actually present in a project file after the rewrite): only confirmed deps are recorded in the ledger, exempted from in-run verification (VexBuildParams.assume_applied), counted as redirected, or allowed through the property-7 bypass. The no-lockfile case now warns, writes no ledger, and a requested attestation fails (exit 1). - record-fetch failures (view endpoint down/404) after a successful redirect were silently swallowed, leaving the patch permanently unattestable with zero signal. They now surface as record_fetch_failed warnings in the JSON envelope and on stderr. - five rewriters recorded an edit even when the entry was already at its target values, growing the committed ledger on every re-run and poisoning a future revert (npm entry, pnpm, Cargo.lock via a tri-state that keeps the pkg-not-found warning honest, nuget lock, gem CHECKSUMS). A second pass over rewritten output now records zero edits; the ledger merge keeps append semantics for real changes. - requirements.txt: the environment marker was captured to end-of-line, swallowing a previously appended --hash and duplicating it on every re-run; markers are now taken from the requirement portion only. pip-compile --generate-hashes backslash continuations are refused with redirect_requirements_continuation instead of corrupted (orphaned --hash lines / mid-marker backslash = pip InvalidMarker). - vendor::load_state treats a mode-tagged NON-vendor ledger squatting on .socket/vendor/state.json (an early depscan GitHub-app registry ledger) as empty instead of bricking remove/vendor/repair with vendor_state_unreadable; genuinely corrupt vendor ledgers stay fail-closed. Assisted-by: Claude Code:claude-fable-5
The fixtures are a byte-for-byte cross-language contract; Windows runners' CRLF checkout conversion made redirect_golden_fixtures_match fail on the generated-vs-expected comparison. -text disables EOL conversion for the fixture tree (contents are already committed LF). Assisted-by: Claude Code:claude-fable-5
The redirect golden fixtures commit manifests/lockfiles that deliberately pin old, vulnerable dependency versions — they are the test inputs for a security-patching tool. Socket's PR scan flagged their CVEs (Puma, Flask) as project dependency alerts; socket.yml now scopes scanning away from the fixture trees. Assisted-by: Claude Code:claude-fable-5
…ixes, golang NO-GO - scan --mode <hosted|vendored|agent> is the documented mode selector; the legacy spellings (--redirect/--vendor/--apply) stay as aliases. Cross-mode combinations error; --detached composes with either vendored spelling. The hosted ledger writes mode: "hosted" (loader stays tolerant of pre-rename strings). - rewrite_maven_pom: hosted maven support for pom projects — surgical <repository> insert (socket-patch-<uuid>, checksumPolicy=fail, snapshots disabled), same-GAV verify-only version check with unpinned/not-found/packaging warnings; gradle build files are never machine-edited (settings-level repos are silently ignored under PREFER_PROJECT) — a paste-able exclusiveContent snippet is emitted instead. Byte-identical to the TS twin via new shared maven/pom golden fixtures. - nuget config correctness: creating a packageSourceMapping now fans a '*' catch-all out to pre-existing sources (else every other package fails NU1100), seeds nuget.org when the sources list is empty, and a self-closing <packageSources /> is expanded in place instead of leaving a duplicate element. New shared golden cases pin all three. - golang hosted is a documented NO-GO (docs/design/golang-hosted-no-go .md): day-2 sumdb failures need uncommittable GOPRIVATE, replacement -module identity would force per-grant artifacts, and the default GOPROXY chain would leak tokened URLs or licensed bytes to a public mirror. The rewriter warning now names the decision and the remedy (vendored mode). Assisted-by: Claude Code:claude-fable-5
…evert - nuget_feed.rs: flat-folder feed at .socket/vendor/nuget/<uuid>/ (relative-path <add> source resolves against nuget.config — committable), packageSourceMapping with the '*' catch-all rule, packages.lock.json contentHash = base64(sha512(nupkg)) recomputed locally. Service-first with local-cache fallback (deterministic re-zip of the patched extraction). dotnet restore --locked-mode turns cache collisions into a loud NU1403. - maven_repo.rs: the vendored uuid dir IS a file:// maven2 repository (jar + REAL upstream pom + sha1/md5 sidecars — an authored minimal pom would drop transitive deps). Multi-module poms and gradle-only projects refuse loudly; warm ~/.m2 shadowing warned with a purge one-liner. - Fragment-level revert for both backends: per-purl removal excises ONLY our wiring fragment, preserving sibling patches' wiring and post-vendor user edits; whole-file restore only on the byte-identical fast path; drift warns per the ledger contract. - Shared plumbing: nuget/maven in ECOSYSTEM_DIRS + dispatch + leaf_to_purl; .nupkg/.jar handled by the zip verifier (VEX/repair); both cargo features promoted to defaults. Assisted-by: Claude Code:claude-fable-5
- Agent mode: VEX legs in all 8 docker apply suites (real container apply → in-container vex → plain-marker statement asserted host-side); dedicated in-process apply tests for npm/composer/maven/nuget; a cross-ecosystem agent-mode VEX matrix (8 ecosystems + qualified PURLs) in e2e_vex.rs. - Vendored mode: docker capstones for the new nuget (fresh-checkout cold-cache --network none restore, NU1403 tamper leg) and maven (offline mvn against the file:// repo, checksum negative, warm-cache shadow warning) backends; host capstones for gem/composer (toolchain- gated, CI-pinned via bundler 2.5 + setup-php); vendored VEX matrix in e2e_vex_vendor.rs including the new backends. - Hosted mode: e2e_redirect_cargo_build capstone (wiremock sparse index + real patched .crate; fresh CARGO_HOME cargo fetch --locked; cksum tamper leg; post-install vex (redirected)); scan --mode parse contract tests. - CI: docker vendor suites wired into the coverage-docker matrix; gem/composer host capstones pinned into the e2e job. Assisted-by: Claude Code:claude-fable-5
README gains 'Choosing a patch mode' — the hosted/vendored/agent comparison table (mechanism, user-code changes, CI requirements, offline story, integrity story), an honest mode × ecosystem support matrix derived from the code, per-mode walkthroughs, VEX provenance- marker documentation (plain = agent, (vendored), (redirected) = hosted), and per-ecosystem caveats (maven ~/.m2 shadowing + purge one-liner, mirrorOf mirrors, nuget locked-mode). Agent mode is positioned as fully supported but not recommended for new setups. CHANGELOG covers hosted mode, --mode, the vendored backends, and the nuget mapping fixes. CLI_CONTRACT adds scan --mode/--redirect and the two ledger surfaces as contract. Assisted-by: Claude Code:claude-fable-5
scan --redirect hosted-vendored-patch mode|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
…t vendors unsupported_ecosystem_purl_is_a_benign_skip used pkg:nuget as its cannot-vendor ecosystem; the new NuGet vendored backend made that purl vendorable (and the feature a default), so the fixture failed as package-not-found instead of skipping benignly. pkg:jsr is the one compiled-in ecosystem with no vendor backend by design — the contract being pinned (benign skip, npm still vendors, exit 0) is unchanged. Assisted-by: Claude Code:claude-fable-5
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
scan_vendor_flag_conflicts_are_clap_errors pins that flag-misuse errors
read like clap's own ('cannot be used with' / 'required'); the --mode
resolution errors said 'cannot be combined' and 'requires', failing the
contract on every CI OS. Exit code was already 2.
Assisted-by: Claude Code:claude-fable-5
A -C checksum failure on the Socket repository does not fail the build when the GAV also exists on Maven Central — Maven falls back and silently installs the unpatched artifact (the patch is dropped, though mismatched bytes are never consumed). Surfaced by the depscan install-verify negative leg; gradle dependency verification or vendored mode remain the strong paths. Assisted-by: Claude Code:claude-fable-5
…ralize pnpm redirect basename Groundwork with no behavior change to existing single-lock flows. Bun grammar: move the conservative `bun.lock` line grammar (BunEntry, parse_packages_section, parse_entry_line, packages_bounds, scan_json_string, scan_balanced_array, split_top_level, decode_json_string, split_name_spec, check_lock_version, SUPPORTED_LOCK_VERSION) out of vendor/bun_lock.rs into a new patch/bun_lock_text.rs. Fields on BunEntry become pub(crate) so both the vendor classifier and lock_inventory can consume it. The vendor-specific classify/TupleShape stay in bun_lock.rs. The grammar's unit test moves with it. lock_inventory.rs now imports the grammar from the shared module. URI: move encode_uri_component out of vendor/yarn_berry_lock.rs into a shared utils/uri.rs as a pub fn. Its impl already matches JS encodeURIComponent exactly (uppercase hex, `-_.!~*'()` unreserved), verified against yarn 4.12 with an added oracle vector. pnpm redirect: generalize rewrite_pnpm_lock to rewrite every files-map key equal to `pnpm-lock.yaml` or ending in `/pnpm-lock.yaml` (e.g. Rush's common/config/rush/pnpm-lock.yaml), iterating keys sorted for stable goldens. The per-dep entry-not-found warning now fires only when the dep matched in no pnpm lock across the whole set. Adds the npm/pnpm/nested-rush-lock golden case (shared by the Rust and TS suites). FileEdit.path and the output-files key are the actual key. Assisted-by: Claude Code:claude-fable-5
…ry fallback, redirect-only repair no-op Rush keeps a single pnpm source-of-truth lock under common/config/rush/ (no root package.json/lock pair) and runs installs from a generated common/temp workspace, so vendor's relative file: rewiring cannot survive. Three disjoint pieces land the read/refuse side of hosted Rush support: - npm_flavor: when no root lockfile matches and rush.json is present, the flavor probe refuses with vendor_rush_unsupported, naming the generated-workspace install model (overrides in pnpm-config.json, installs from common/temp) and routing to `scan --mode hosted`. The check fires only in the otherwise-missing arm, so a stray rush.json beside a real root lock still routes normally. - lock_inventory: inventory_npm_lock (and thus inventory_project) falls back to the Rush locks when the root lock is absent but rush.json exists — the common source-of-truth lock plus every common/config/subspaces/*/pnpm-lock.yaml, read_dir sorted for determinism, repo-relative paths preserved. - repair: a project whose only trace is .socket/vendor/redirect-state.json (hosted mode; no manifest, no vendor ledger, no vendored references) is a no-op for repair, not a manifest_not_found error. Exit success with an informational redirect_only_project skip routing to `scan --mode hosted`; contents are not validated. Assisted-by: Claude Code:claude-fable-5
…red goldens
Adds `rewrite_yarn_berry` and `rewrite_bun_lock` to the registry-redirect
engine, the Rust half of the cross-language rewriter contract with the
depscan backend's TS `registry-rewrite/{yarn-berry,bun}` twins.
- Berry: rewrites ONLY the lock entry — `resolution:` gains yarn's own
`::__archiveUrl=<encodeURIComponent(url)>` binding and `checksum:` becomes
the override's `yarnBerry10c0` (berry verifies the converted cache zip, not
the tarball). Descriptor key + package.json untouched, so `--immutable`
still passes. Whole-file gates refuse a cacheKey != 10c0 or a
`.yarnrc.yml compressionLevel != 0` (no offline-reproducible checksum).
- Bun: rewrites a registry 4-tuple `["name@ver","<reg>",{deps},"sha512-…"]`
to a URL 3-tuple `["name@<url>",{deps verbatim},"<sha512>"]` using the
shared `bun_lock_text` grammar (fail-CLOSED on any deviation). Binary
`bun.lockb` is never parsed — presence without a text lock is a refusal.
Content-based dispatch: berry is detected by `^__metadata:` (classic already
declines it). New golden fixtures under `tests/fixtures/redirect/npm/{yarn-
berry,bun}/` (authored TS-side, byte-matched here); `RUST_IMPLEMENTED` gains
both. Warning-path unit tests cover every refusal branch.
Assisted-by: Claude Code:claude-fable-5
Wires the CLI's `scan --redirect` for the new berry/bun rewriters:
- `REDIRECT_CANDIDATE_FILES` gains `.yarnrc.yml` (berry cache-config gate)
and `bun.lock`.
- The DepOverride merges the reference's `yarn-berry-zip` artifact —
`yarnBerry10c0` into integrity and `berry_zip_url` from its URL — so the
berry rewriter can pin the cache-zip checksum.
- Confirmed-redirect gating also matches `encode_uri_component(artifact_url)`,
since the berry rewriter writes the URL percent-encoded into the lock's
`::__archiveUrl=` binding (the raw form never appears there).
- bun.lockb auto-migration in `run_redirect`, before candidate reads: a
binary lockfile with no text `bun.lock` is re-locked via `bun install
--save-text-lockfile --frozen-lockfile --lockfile-only` (writes bun.lock,
deletes bun.lockb, offline, fails closed). The removal is recorded as a
ledger `FileEdit { action: "removed" }` (git history is the restore path);
a dry-run only warns, and a failure degrades to the rewriter's own
presence-only refusal.
In-process tests cover the berry leg (encoded __archiveUrl + yarnBerry10c0 +
preserved key + idempotent re-run), the bun leg (4-tuple → URL 3-tuple), and
the lockb migration via a fake `bun` shim on PATH.
Assisted-by: Claude Code:claude-fable-5
…te warning Rush monorepos have no root package.json/lock pair: the single pnpm source-of-truth lock lives at common/config/rush/pnpm-lock.yaml and, when subspaces are enabled, one lock per subspace under common/config/subspaces/<name>/. After the static REDIRECT_CANDIDATE_FILES loop, run_redirect now reads those locks (when rush.json is present) into the files map under their repo-relative keys — the pnpm rewriter is basename-generalized, so nested locks are rewritten in place and the path-generic write-back / confirmed-gating already handle them. Discovery of the packages themselves flows through the lock_inventory Rush fallback landed earlier. When at least one Rush lock is read and common/config/rush/repo-state.json exists, the run emits redirect_rush_repo_state_stale: editing the lock outside `rush update` desyncs the pnpmShrinkwrapHash that file records, so `rush install` fails until `rush update` refreshes it if preventManualShrinkwrapChanges is enabled — but the redirect survives that refresh (pnpm keeps locked resolutions for unchanged specifiers). repo-state is Rush's to manage; the redirect never touches it. Tests: a rush-shaped in_process_redirect fixture (rush.json + common lock + subspace lock, no root pair) asserts both nested locks are repointed in place; a subprocess JSON test pins the stale warning present-with-repo-state and absent in the twin without it. Assisted-by: Claude Code:claude-fable-5
Add `in_process_redirect_pnpm.rs`: the plain single-project pnpm
(lockfileVersion 9.0) root-lock counterpart of the npm hosted-redirect
legs. Asserts the `resolution:` splice to `{integrity: <patched>,
tarball: <hosted>}` (shared `npm/pnpm` golden shape), the
`redirect_pnpm_resolution` ledger edit, idempotency, and the `--vex`
`(redirected)` attestation.
Add `repair_vendor_flavors_e2e.rs`: generalize the npm-classic repair
invariants (`repair_vendor_e2e.rs`) across pnpm / yarn-berry / bun
vendored projects — deleted-tarball rebuild, corrupt-tarball rebuild,
tampered-ledger-sha fail-closed, and ledger-gone reconstruction from the
lockfile's vendored-tarball reference. Fixtures run the real
`scan --vendor` flow in-test against a mock API (no real package
manager) using each backend's pre-vendor lock shape.
Assisted-by: Claude Code:claude-fable-5
Extend `in_process_alternate_installers.rs` with three real-layout
agent-mode apply legs:
- bun: real `bun install` (private cache) → apply patches the hoisted
node_modules copy;
- yarn-berry node-modules linker: `.yarnrc.yml nodeLinker: node-modules`
+ corepack-dispatched `yarn@4.12.0`, install → apply (complements the
PnP refusal test in `e2e_safety_yarn_pnp.rs`);
- rush pnpm symlink farm: a hand-built
`common/temp/node_modules/.pnpm/<pkg>@<v>/…` store with per-project
`apps/{a,b}/node_modules/<pkg>` symlinks and `rush.json` at the root;
apply at the root patches the canonical `.pnpm` file once and the
bytes are visible through BOTH symlinks — pinning that the crawl finds
the package via the project symlinks even though `common/temp` is in
SKIP_DIRS.
Assisted-by: Claude Code:claude-fable-5
Overhaul the maven pom rewriter to consume the new server-side version-suffixing contract. When a maven2 override carries `mavenSuffixedVersion` + `mavenPomSha256`, pin the patched jar to the Socket-only suffixed version explicitly — rewrite the literal `<version>` or, for a transitive/managed dependency, author a `<dependencyManagement>` entry — so an outage or tamper on the Socket repo hard-fails the build instead of silently resolving the unpatched upstream artifact. A property version is refused (a literal edit would break the reference); a mismatched literal version is skipped. When a pin lands and both the jar and served-pom sha256 are known, emit Maven Trusted Checksums files (`.mvn/maven.config` resolver args + `.mvn/checksums/checksums.sha256` entries), merging into any existing user config/checksum set (dedupe by key, never override a conflicting value, sort checksums by path). Absent a suffixed version, fall back to today's same-GAV repository injection with a `redirect_maven_same_gav_fallback` warning (not fail-closed). Add `maven_suffixed_version` + `maven_pom_sha256` to the identifiers struct, list the two `.mvn/*` files in REDIRECT_CANDIDATE_FILES, and confirm a maven redirect by the globally-unique suffixed version string (the `.pom` URL never lands in a pom). The Gradle manual snippet now pins the suffixed version and reminds the user to bump the declaration. Update the shared golden fixtures (basic, existing-repositories, property-version-warn now a zero-edit refusal, rerun-noop) and add new cases (transitive-depmgmt, existing-depmgmt, no-suffix-fallback, mvn-config-merge, version-mismatch-skip). The Rust rewriter is byte-identical to the TS twin. Assisted-by: Claude Code:claude-fable-5
Add `e2e_redirect_yarn_berry_build.rs` and `e2e_redirect_bun_build.rs`, the hosted-mode (`scan --mode hosted`) full-chain analogs of `e2e_redirect_npm_build.rs` for the yarn-berry and bun flavors: `scan --mode hosted` rewrites the lock to resolve the patched dep from the wiremock hosted tarball, then a fresh-checkout install (`yarn install --immutable --check-cache` / `bun install --frozen-lockfile`, offline from the registry) MUST land the patched bytes; a tamper twin serving different bytes must fail (YN0018 / integrity). berry derives the exact `10c0/<hex>` cache-zip checksum the mock hands back by a bootstrap real-yarn `resolutions: file:` install (yarn recomputes the same zip checksum for the archiveUrl locator). Two harness robustness measures: the corepack gate probes from a neutral tempdir (the monorepo root's `packageManager` field otherwise makes corepack refuse yarn), and every yarn invocation sets `YARN_ENABLE_GLOBAL_CACHE=false` so the persistent `~/.yarn/berry` cache can't serve the tampered twin honest bytes. `bun.lockb` migration is not exercised here (bun 1.3.x has no flag to emit the binary lockfile); it stays covered by the in-process shim test `scan_redirect_migrates_bun_lockb_then_redirects`. Assisted-by: Claude Code:claude-fable-5
Dockerfile.npm: cache `yarn@4.12.0` in corepack WITHOUT `--activate` (the global `yarn` stays classic 1.22.22); berry fixtures opt in per-project via package.json `"packageManager": "yarn@4.12.0"`, which the corepack shim dispatches. A build-time check pins that the shim resolves yarn 4 under a packageManager pin while the global stays classic. docker_e2e_npm.rs: add two berry legs — an agent-mode install→apply chain (container twin of the npm agent leg with a yarn 4 install front-end, reusing the shared mock fixture) and a vendored offline-frozen-install chain (container twin of `e2e_vendor_yarn_berry_build.rs`: stage a manifest from the installed bytes, `vendor --offline`, then a fresh-checkout `yarn install --immutable --check-cache` must land the patched bytes — proving the CLI's offline `10c0/<hex>` checksum is what real yarn 4 accepts). Both gated behind the existing `docker-e2e` feature + `skip_if_no_docker_image`. Assisted-by: Claude Code:claude-fable-5
…rush) Add `e2e_redirect_rush_sim.rs`. Tier 1 (default-runnable, gated on corepack pnpm@9): run the real CLI `scan --mode hosted` over a committed Rush-shaped fixture (rush.json + common/config/rush/pnpm-lock.yaml), then REPLICATE `rush install` in-test — copy the rewritten common lock to common/temp/pnpm-lock.yaml, write a generated-style common/temp/package.json, and run `pnpm@9 install --frozen-lockfile` with the registry pinned to a dead port so the only reachable artifact URL is the wiremock hosted tarball. Asserts the patched bytes land in common/temp/node_modules, plus a tamper twin (serve wrong bytes → pnpm integrity failure). Tier 2 (gated on `RUSH_E2E=1`, network-dependent, off by default): real `npm x @microsoft/rush` — `rush update` → `scan --mode hosted` → `rush install`, asserting patched bytes, then the preventManualShrinkwrapChanges failure + `rush update` recovery. All `RUSH_*` env vars (including the gate) are stripped before invoking rush since it rejects unrecognized ones. Assisted-by: Claude Code:claude-fable-5
Silence `-D warnings` in the new redirect capstones: fold the berry
bootstrap-checksum `let...else { return None }` into the `?` operator,
and drop the unused `copy_dir_recursive` helper from the rush sim (it
rewrites locks in place and never copies a `.socket/` tree).
Assisted-by: Claude Code:claude-fable-5
… scan aliases README: support matrix hosted npm-family cell now covers yarn-berry (cacheKey 10c0 / compressionLevel 0, node-modules linker e2e-covered, PnP untested for hosted) + bun (text bun.lock v1, bun.lockb auto-migrated); adds a Rush note (hosted repoints common/config lockfiles in place incl. subspaces, agent works through the symlink farm, vendored refused → hosted). The maven caveats section is rewritten from the old silent- fallback story to fail-closed version suffixing (a Socket-only <version>-socket.<hex8> the mirror/warm-~/.m2 can't shadow, build hard- fails on outage/tamper) plus optional Maven 3.9+ Trusted Checksums (silently inert below 3.9; 3.9.9 MNG-8182 readability fix); ~/.m2 shadow re-scoped to vendored-only, mirrorOf flipped to a loud failure, gradle snippet now carries the suffixed version. Legacy scan spellings --vendor /--apply/--sync are removed from every example and prose line; scan --mode <hosted|vendored|agent> is the only documented selector (the booleans stay as back-compat aliases, noted once). The standalone vendor command section stays (home of vendor --revert). VEX (redirected) marker docs unchanged (still accurate — nothing user-visible changed). CHANGELOG: rewrites the stale verify-only maven-hosted entry to the fail- closed suffixing + trusted checksums behavior; adds Unreleased entries for berry/bun hosted (+ bun.lockb auto-migration), Rush hosted/agent/vendored stances, the pnpm nested-lockfile generalization, and the repair redirect- only exit-0 no-op fix. CLI_CONTRACT: documents the redirect candidate-file set (.yarnrc.yml, bun.lock, .mvn/*), berry/bun/rush hosted coverage and the new redirect_* codes, the Rush lockfile-supplement discovery fallback, and repair's redirect_only_project exit-0. Assisted-by: Claude Code:claude-fable-5
The berry/bun rewriter unit tests built single-override slices with `&[ovr.clone()]`, which trips clippy::cloned_ref_to_slice_refs under the --tests lint gate. Use std::slice::from_ref instead — no clone, same slice. Assisted-by: Claude Code:claude-fable-5
Two CI-only failures: - scan_redirect_migrates_bun_lockb_then_redirects is unix-only now: its fake bun shim is a #!/bin/sh script (Windows would need a .cmd twin and ';' PATH joining). The migration code path itself is OS-agnostic and keeps real-bun coverage in the gated e2e capstone. - in-process vendor/revert runs acquire the apply lock with a 5s timeout. flock guards are OFD-based, so a concurrent test's fork()ed pre-exec child briefly holds copies of every parent fd — including a just-dropped lock fd — making back-to-back in-process runs see their own lock as held for the fork→exec window. Seen twice as rare release-only lock_held failures (revert_round_trip previously, revert_works_without_manifest now); the wait absorbs the window via the acquire loop's 100ms retry while a real deadlock still fails. Assisted-by: Claude Code:claude-fable-5
What
This PR ships the hosted patch-apply mode end to end and consolidates the CLI around three clearly-named modes, each fully VEX-attested and behaviorally tested across Socket's supported ecosystems:
scan --mode hosted)scan --mode vendored).socket/vendor/, lockfiles rewired locallyscan --mode agent).socket/manifest.json+ blobs committed; the CLI applies in CI/postinstall--modeis the documented selector; the legacy spellings (--apply,--vendor,--redirect) remain as aliases. The depscan companion PR (SocketDev/depscan#22093) carries the backend registry endpoints, byte-identical TS rewriters, the GitHub-app hosted mode (patchApplyMode: 'agent' | 'hosted'), and the live-environment harness.Highlights
Hosted mode
exclusiveContentsnippet (build scripts are not machine-edited; settings-level repos are silently ignored underPREFER_PROJECT, so surgical edits can't be made safe).docs/design/golang-hosted-no-go.md): day-2 sumdb failures need uncommittableGOPRIVATE; replacement-module identity would force per-grant artifacts; the defaultGOPROXYchain would leak tokened URLs (or licensed bytes) to Google's public mirror. The loud warning points at vendored mode; the GOPROXY serve routes remain documented as an ephemeral-CI recipe..socket/vendor/redirect-state.json,mode: "hosted") merges append-only and embeds patch records so post-installvexhash-verifies redirected patches.packageSourceMappingcorrectness: adding a mapping now fans out a*catch-all to pre-existing sources (and seeds nuget.org when the sources list is empty) — without it, every OTHER package fails NU1100.Vendored mode
contentHashrecompute;dotnet restore --locked-modegives a fail-closed NU1403 on cache collisions. Proven in a real Docker container (fresh checkout, cold caches,--network none).file://maven2 repository (jar + REAL upstream pom + sidecars); multi-module and gradle-only projects refuse loudly; warm-~/.m2shadowing warned with a purge one-liner. Docker-proven offline with a transitive-dep artifact.VEX
(vendored),(redirected)(hosted) — per-mode cross-ecosystem matrices (8 official ecosystems + qualified PURLs) plus VEX legs in every real-environment capstone.Test matrix (each cell a real test)
Docs
~/.m2shadowing,mirrorOfmirrors, nuget locked-mode). CHANGELOG + CLI_CONTRACT now cover--mode, hosted mode, and the ledger contracts.Merge coordination
Merge this first; the depscan PR re-points its submodule to the squash-merged commit before landing.
Update: yarn-berry + bun hosted support, Rush monorepos, maven fail-closed hardening
yarn-berry + bun hosted rewriters (new, both twins byte-identical via shared goldens)
resolution: "name@npm:<ver>::__archiveUrl=<encoded url>"+checksum: 10c0/<hex>from the precomputed berry cache-zip checksum. package.json untouched, soyarn install --immutable --check-cachepasses; tamper is a hard YN0018. Requires yarn 4'scacheKey: 10c0(compressionLevel 0) — anything else refuses loudly. Oracle-verified against real yarn 4.12 (yarn itself writes the identical encoding + checksum).bun.lockv1 — registry 4-tuple rewritten to a URL 3-tuple with sha512 SRI;bun install --frozen-lockfilepasses, tamper is a hard IntegrityCheckFailed.bun.lockb-only projects are auto-migrated byscan --mode hostedvia the user's own bun (--save-text-lockfile --frozen-lockfile --lockfile-only, offline-capable, fail-closed on drift; recorded in the ledger).npm/yarn-berry(9 cases) +npm/bun(8 cases); real-install hosted capstones (e2e_redirect_yarn_berry_build,e2e_redirect_bun_build) with tamper twins; docker image gains corepack yarn@4 with berry vendored/agent legs.Rush (pnpm) monorepos
scan --mode hosteddiscoverscommon/config/rush/pnpm-lock.yaml+ subspace locks (the pnpm rewriter is now basename-generalized for nested lock paths); warnsredirect_rush_repo_state_stalewhen repo-state.json is present (preventManualShrinkwrapChanges interplay). Hosted URLs survive Rush's copy-to-common/tempinstall; proven by a rush-sim capstone + a gatedRUSH_E2E=1real-rush leg.vendor_rush_unsupported) — Rush's generated-workspace install model breaks relativefile:wiring; the error routes to hosted mode.repairon redirect-only projects is now an informational no-op instead ofmanifest_not_found.Maven hosted: fail-closed via version suffixing + Trusted Checksums
<version>-socket.<uuid8>— a version that exists ONLY on the Socket repository. A repo outage or tampered bytes is now a hard build failure (Maven consults Central for the suffixed GAV and cannot find it) instead of a silent fallback to the unpatched artifact. Base-version paths 404; no maven-metadata.xml is published so version ranges can never discover suffixed versions.<dependencyManagement>pin for transitives;${property}versions refuse per-dep. When the upstream pom is unavailable, serving falls back to same-GAV with an explicitredirect_maven_same_gav_fallbackwarning..mvn/maven.config+.mvn/checksums/checksums.sha256(Maven 3.9+ native Trusted Checksums; sha256-pins jar + pom, inert pre-3.9 where suffixing still holds). The strict contract is pinned by rewritten registry-redirect legs (tamper/outage MUST fail) and a trusted-checksums CI leg in the installer suite.Also
--vendor/--applyspellings (--modeis the documented selector; flags remain as hidden aliases), support matrix + maven caveats rewritten to the fail-closed story.