Repository navigation
Hosted cargo vex attests not_affected while Cargo.lock also builds an unpatched crates.io copy of the same crate@version #679
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 Oct 3, 2026 mikolalysenko commented
on Oct 3, 2026 CollaboratorAuthorMore actions[agent] Triaged: priority:p2 (Cargo). Shares the "same-lock unwired sibling contests the Socket ref" class with npm #588 (PR #589), but the fix lives in a different extractor (
vex/discover/cargo.rs::hosted_from_lock), so it is tracked separately. No open or merged PR addresses it yet.
Generated by Claude Code
mikolalysenko commented
on Oct 5, 2026 CollaboratorAuthorMore actions[agent] Follow-up from the Cargo bug-hunt routine (ledger #315), on main
99f61d2. No cargo vex code changed since the report, and I didn't re-runvexthis time. What's new on the same contested lock:- Re-running
scan --mode hostedexits 0 withredirected: 0and aredirect_cargo_transitive_dependentswarning saying cfg-if "was NOT redirected". But the existing Socket pin stays live inCargo.toml/Cargo.lock, and the root crate keeps building it. So the only signal is a warning that describes the state wrongly. It never says that the crates.io sibling now builds unpatched beside the pin. list --patch-server-url …showspkg:cargo/cfg-if@1.0.4asdiscovered, with no contested-lock warning.repairhas nothing to do (manifest_not_found, since hosted keeps no manifest).remove pkg:cargo/cfg-if@1.0.4on this lock exits 0 but writes a duplicate crates.iocfg-if 1.0.4block, which cargo refuses to parse. Filed separately as Hosted cargo remove writes a duplicate cfg-if block into Cargo.lock when crates.io also locks the same crate@version, so cargo can no longer parse the lock while remove reports success #863.- A path sibling (an in-tree
sibcrate depending oncfg-if = "=1.0.4") produces the same contested lock as a registry dependent. The contested lock also forms on cargo 1.74.1 (lock v3) and on 1.97.0 (v3 and v4).
Generated by Claude Code
- Re-running
- addedv5-blockerMust resolve before v5: public interface/migration or ordinary patch-install-undo failure.Must resolve before v5: public interface/migration or ordinary patch-install-undo failure.compatibilityPublic CLI/JSON, saved state, upgrades, or package-manager compatibility.Public CLI/JSON, saved state, upgrades, or package-manager compatibility.and removed
on Oct 9, 2026 mikolalysenko commented
on Oct 9, 2026 CollaboratorAuthorMore actionsv5 release blocker (P1). Ordinary Cargo graph changes can leave both hosted and registry copies. VEX must not claim the whole package version is patched when the build consumes an unpatched copy.
This follows the maintainer's release scope: one normally completing CLI instance, prioritizing valid-lockfile patch/install behavior, compatibility, and actionable CLI UX.
mikolalysenko commented
on Oct 9, 2026 CollaboratorAuthorMore actions[agent] Claiming for v5 blocker burn-down (shared root cause: hosted cargo lock holding both a Socket and a crates.io copy of the same name@version (vex attests, restore duplicates block)). Branch: agent/v5-cargo-contested-lock. Claim-ID: 2026-10-09T16:41:36Z-317298
- added a commit that references this issue
on Oct 9, 2026
[agent] Found by the scheduled Cargo bug-hunt routine (ledger #315).
Summary
After
scan --mode hostedpinscfg-if 1.0.4to its per-patch sparse registry, a dependency added later that also depends oncfg-ifresolves its own crates.iocfg-if 1.0.4. Cargo can't unify packages from different sources, soCargo.lockthen holds twocfg-if 1.0.4entries: the Socket one, used by the root crate, and the crates.io one, used by the new dependency. The build compiles both, so the new dependency links the unpatched bytes.socket-patch vexstill emitspkg:cargo/cfg-if@1.0.4asnot_affected("Patched via Socket patch … (redirected)") with no warning.scanalready refuses this exact graph up front (redirect_cargo_transitive_dependents, "crc32fast compiled the unpatched crates.io copy while scan reported the crate redirected"). Post-scanvexhas no equivalent guard, so the same graph is attested once it appears after the scan, for example aftercargo add,cargo updateor a merge.Impact
This is a false VEX attestation: the shipped binary contains the vulnerable crate, and the VEX document says it doesn't. Adding a dependency after a hosted scan is the normal workflow. Cargo picks the same version whenever the patched version is the newest compatible release, which is common for a fresh security patch.
Repro (Linux, cargo 1.93.1 and 1.97.0)
I used a scratch copy of
crates/socket-patch-cli/tests/e2e_redirect_cargo_shapes.rs(the wiremock sparse-registry harness), with the plaincfg-if = "1.0.4"consumer shape. After the harness's fresh checkout pluscargo fetch --lockedandcargo build --locked --offline:Resulting
Cargo.lock:cargo tree -i cfg-if@1.0.4reports the spec as ambiguous, listing both sources. Thevexoutput (exit 0):Reproduced 3 times: twice on 1.93.1 and once on stable 1.97.0.
Expected vs actual
name@versionfrom a non-Socket source beside the Socket wiring, the reference is dropped (patched_ref_unattributable) or the lockfile basis is withheld. The vlt row spells out the same-lock case: "A same-name@versionnode on another registry … keeps the reference but withholds the lockfile basis: only an installed tree whose every store copy verifies attests". The redirect refusal inscandocuments that this graph builds an unpatched copy.not_affected.Matrix
First bad release: not bisected. Main is
045d7ec.Suspect code
crates/socket-patch-core/src/vex/discover/cargo.rs:435(hosted_from_lock): there's no check for another[[package]]with the same name+version and a non-Socketsource. Comparecargo_unpinnable_dependentsatcrates/socket-patch-core/src/patch/redirect/mod.rs:1694, whichscanuses to refuse the same graph.