[agent] Found by the scheduled Cargo bug-hunt routine (ledger #315).
Summary
After scan --mode hosted pins cfg-if 1.0.4 to its per-patch sparse registry, a dependency added later that also depends on cfg-if resolves its own crates.io cfg-if 1.0.4. Cargo can't unify packages from different sources, so Cargo.lock then holds two cfg-if 1.0.4 entries: 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 vex still emits pkg:cargo/cfg-if@1.0.4 as not_affected ("Patched via Socket patch … (redirected)") with no warning.
scan already refuses this exact graph up front (redirect_cargo_transitive_dependents, "crc32fast compiled the unpatched crates.io copy while scan reported the crate redirected"). Post-scan vex has no equivalent guard, so the same graph is attested once it appears after the scan, for example after cargo add, cargo update or 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 plain cfg-if = "1.0.4" consumer shape. After the harness's fresh checkout plus cargo fetch --locked and cargo build --locked --offline:
# fresh checkout of the hosted-scanned project
sed -i 's/^\[dependencies\]$/[dependencies]\ncrc32fast = "=1.5.0"/' Cargo.toml
cargo build # crc32fast pulls crates.io cfg-if
cargo update -p cfg-if@1.0.5 --precise 1.0.4 # = the case where the patched version is the newest release
cargo build --locked # ok: "Downloaded cfg-if v1.0.4 … Compiling cfg-if v1.0.4" (crates.io)
socket-patch vex --output doc.vex.json --product pkg:cargo/consumer@0.1.0 \
--patch-server-url $MOCK --api-url $MOCK --org o --api-token fake --cwd .
Resulting Cargo.lock:
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2…"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "sparse+http://127.0.0.1:34849/patch-registry/cargo/<token>/c1f90104-…/index/"
checksum = "cb5ea012…"
[[package]]
name = "crc32fast"
version = "1.5.0"
dependencies = [
"cfg-if 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
cargo tree -i cfg-if@1.0.4 reports the spec as ambiguous, listing both sources. The vex output (exit 0):
"products": [{"@id": "pkg:cargo/consumer@0.1.0", "subcomponents": [{"@id": "pkg:cargo/cfg-if@1.0.4"}]}],
"status": "not_affected",
"impact_statement": "Patched via Socket patch c1f90104-… (redirected)"
Reproduced 3 times: twice on 1.93.1 and once on stable 1.97.0.
Expected vs actual
- Expected (CLI_CONTRACT, "Manifest-less VEX", Contested locks): when the build resolves the same
name@version from 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@version node on another registry … keeps the reference but withholds the lockfile basis: only an installed tree whose every store copy verifies attests". The redirect refusal in scan documents that this graph builds an unpatched copy.
- Actual: the cargo extractor accepts the Socket-sourced entry and ignores the crates.io sibling. Verification hashes only the Socket-registry source dir, so the purl is attested
not_affected.
Matrix
| OS |
cargo |
lock |
Reproduces |
| Linux |
1.93.1 |
v4 |
yes (×2) |
| Linux |
1.97.0 (stable) |
v4 |
yes |
| macOS / Windows |
any |
– |
untested (the logic is OS-independent) |
First bad release: not bisected. Main is 045d7ec.
Suspect code
[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.