Agent-mode cargo apply patches only the first of several registry/src index dirs (cargo 1.84 vs 1.85+ hashes), so one cargo keeps building the unpatched crate while apply and VEX report success #339
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:cargoCargoCargo
on Sep 30, 2026 mikolalysenko commented
on Sep 30, 2026 CollaboratorAuthorMore actions[agent] Triaged:
priority:p2(Cargo). Not a duplicate, and no open or merged PR fixes it. Confirmed on mainf6b7fb9: the cargo arm ofdispatch_find(ecosystem_dispatch.rs) merges withmerge_first_wins, andget_registry_src_pathsreturns the index dirs inread_dirorder, so only one extracted copy per purl is patched. #338 is in the same crawler (get_crate_source_paths), but its cause is different: it never reads.cargo/config*source replacement. So the two are cross-linked, not clustered. A fix to one should keep the other in mind, since both change which copiesapplytargets.
Generated by Claude Code
mikolalysenko commented
on Oct 1, 2026 CollaboratorAuthorMore actions[agent] Re-triage on main
2463257(after the v5 consolidation in #277): still reproduces. It also affects global mode.I set
CARGO_HOMEto a dir with two index dirs,registry/src/index.crates.io-1949cf8c6b5b557f/cfg-if-1.0.4andregistry/src/index.crates.io-6f17d22bba15001f/cfg-if-1.0.4, then ransocket-patch scan -g --mode agent --ecosystems cargo --json. It reportsapplied: 1withstatus: success, but only the1949cf…copy has the patchedsrc/lib.rs. The6f17d22…copy, which cargo ≤1.84 builds from, is untouched.CargoCrawler::crawl_allstill dedupes by PURL across index dirs (seenset,crates/socket-patch-core/src/crawlers/cargo_crawler.rs), so-g/--global-prefixruns hit the same first-dir-wins behaviour as project runs.
Generated by Claude Code
mikolalysenko commented
on Oct 2, 2026 CollaboratorAuthorMore actions[agent] Re-checked on main
045d7ec, after #517 ("Fix agent vex checking only one installed copy", #516). This issue still reproduces: with tworegistry/src/index.crates.io-*dirs that each hold an unpatchedcfg-if-1.0.0,apply --offlinereports "1 of 1 targeted patch applied" but writes only the-1949cf8c6b5b557fcopy (the-6f17d22bba15001fcopy stays at the original bytes), andvex --offlinestill emitsnot_affected. The #516 every-copy rule doesn't reach cargo, which suggests the cargo crawler still returns only one copy per crate@version.
Generated by Claude Code
mikolalysenko commented
on Oct 8, 2026 CollaboratorAuthorMore actions[agent] Re-checked on main
9472be4. This still reproduces, and it now also reaches the CI gate that #1029 extended to cargo:apply --checkpasses over the unpatched copy.Repro (Linux, cargo 1.93.1, private
CARGO_HOME, agent mode via a local patch-API stand-in):scan --mode agentpatchesregistry/src/index.crates.io-1949cf8c6b5b557f/cfg-if-1.0.4/src/lib.rs.- I added
registry/src/index.crates.io-6f17d22bba15001f/cfg-if-1.0.4with the original bytes. That's the dir cargo ≤1.84 builds from. socket-patch apply --check --jsonreturnedstatus: success, one eventskipped/already_patched, exit 0, twice.
For comparison,
apply --checkon a single-copy cache correctly fails when that one copy'ssrc/lib.rsis put back to the original (failed/not_applied, exit 1). So the new check works per copy, but it only sees the copy the crawler's first-dir-wins dedupe returns (CargoCrawler::crawl_all'sseenset), the same root cause as above. A cargo ≤1.84 CI job that gates onapply --checkpasses, then builds the unpatched crate.
Generated by Claude Code
mikolalysenko commented
on Oct 9, 2026 CollaboratorAuthorMore actionsv5 triage: P2, not a release blocker. Retain multi-toolchain agent cache-copy selection at P2. This is real, but not a blocker for the default hosted/vendored first-run workflow.
This follows the maintainer's release scope: one normally completing CLI instance, prioritizing valid-lockfile patch/install behavior, compatibility, and actionable CLI UX.
[agent] Found by the scheduled Cargo bug-hunt routine (ledger #315).
Summary
$CARGO_HOME/registry/src/routinely holds more than one extracted copy of the same crates.io crate, one per registry-source directory:index.crates.io-6f17d22bba15001fis used by cargo 1.70–1.84.index.crates.io-1949cf8c6b5b557fis used by cargo ≥ 1.85, where the source-id hash changed.github.com-1ecc6299db9ec823is the git index, the default before 1.70.The agent-mode crawler lists every one of those directories, but the cargo arm of
dispatch_findmerges withmerge_first_wins. Soapplypatches only the first copy inread_dirorder, and reportsapplied/success. A project whose cargo reads one of the other directories keeps building the unpatched crate, andvexstill attestsnot_affected.This happens on any machine where two cargo versions share a
CARGO_HOME. A typical case is a project pinned byrust-toolchain.tomlto ≤ 1.84 on a machine whose default stable toolchain is ≥ 1.85.Impact
applyexits 0 withsuccess, and VEX saysnot_affected, butcargo build --lockedfor the pinned-toolchain project compiles the vulnerable sources. The docs promise the opposite ("the patch affects every project on the machine", docs/ecosystems.md "Cargo: shared registry cache", README.md and CLI_CONTRACT.md).Repro
The patch is a hand-staged
.socket/(stage.pyis below). It appendspub fn socket_patched()tocfg-if, andmain.rscalls it.stage.pywrites.socket/manifest.json(setup.manual = ["cargo"], one filesrc/lib.rswithbeforeHash/afterHashas git-blob SHA-256) plus both blobs:Which copy gets patched depends only on the directory listing order, not on which cargo the project uses. With the order above, a project pinned to 1.93 is patched and one pinned to 1.84 isn't. If the order were reversed, it would be the other way round.
Expected vs actual
$CARGO_HOME/registrycache: the patch affects every project on the machine." So every extracted copy ofname@versionunderregistry/src/*should be patched, the waymerge_variant_copiesalready does for gem's coexisting stores. At a minimum,applyshould warn that other copies remain, andvexmust not attest when the copy the project's cargo reads is unpatched.applyreportssuccess, andvexattestsnot_affected.Matrix
The project is pinned with
rust-toolchain.toml. The sharedCARGO_HOMEwas filled by 1.93.1 and then by 1.84.1.ubuntu-latest)ubuntu-latest)macos-latest)macos-latest)windows-latest)windows-latest)On every OS, one of the two cargos is left unpatched while
applysayssuccess. Which one it is depends on the filesystem's listing order: on the GitHub Linux and macOS runners it's the current 1.93 toolchain that ends up unpatched.Versions
f6b7fb9(CLI 4.0.0): fails as above.applyreportspartialFailureand patches nothing, so there was no false success there. I didn't bisect further.Suspect code
crates/socket-patch-cli/src/ecosystem_dispatch.rs:290-302: the cargoscan_ecosystem!useson_match = merge_first_wins, so only one path per purl is kept across source roots.crates/socket-patch-core/src/crawlers/cargo_crawler.rs:272-283:get_registry_src_pathsreturns the index dirs in unsortedread_dirorder.Probe run (the workflow on the throwaway branch
bughunt/cargo/20260930-index-dirs): https://github.com/SocketDev/socket-patch/actions/runs/36742610070Backlog review — 2026-10-08
Priority: P2 → P1. Multiple Cargo registry cache hashes leave a consumed copy unpatched while VEX reports success.