[agent] Found by the scheduled Cargo bug-hunt routine (ledger #315).
Summary
Agent mode patches a crate's sources in place, in $CARGO_HOME/registry/src/<index>/<crate>-<ver>/ or in a cargo vendor directory source. Cargo doesn't fingerprint registry or directory-source packages by file mtime or content. It treats them as immutable and keys their build cache on the package id. So if the project was built before apply (the normal case for a developer checkout, or a CI job with a cached target/), the next cargo build --locked just reuses the unpatched libcfg_if-*.rlib in target/ and never recompiles the patched sources.
Meanwhile apply reports success / applied: 1, and vex (with setup.manual: ["cargo"]) emits not_affected, because the on-disk file hashes do match afterHash. The binary the user ships still contains the vulnerable code. Only cargo clean -p <crate> (or a full cargo clean) makes the patch take effect.
rollback has the same problem in reverse: after apply → build → rollback, the next build still links the patched rlib.
Vendored and hosted modes aren't affected, because they change the package's source id (path dependency or per-patch registry), which forces a rebuild.
Impact
It's a silent false fix, and VEX attests it. Any machine or CI cache that built the project before apply keeps shipping the unpatched crate, with no warning anywhere. README / docs/ecosystems.md ("Cargo: shared registry cache") warn that the in-place patch is shared and reset by cargo clean, but say nothing about already-compiled artifacts ignoring the patch.
Repro (Linux, any cargo)
export CARGO_HOME=$PWD/cargo-home
cargo new -q app && cd app
cat >> Cargo.toml <<'T'
cfg-if = "=1.0.0"
T
cargo build -q # baseline: cfg-if compiled into target/
# stage .socket/manifest.json + blobs for pkg:cargo/[email protected] whose afterHash
# appends `/// m\npub fn socket_patched() -> u32 { 1 }` to src/lib.rs,
# with "setup": {"manual": ["cargo"]} (same shape as tests/e2e_vendor_cargo_build.rs::stage_patch)
socket-patch apply --offline --json # status success, applied 1
echo 'fn main() { println!("M:{}", cfg_if::socket_patched()); }' > src/main.rs
cargo run -q --locked --offline # error[E0425]: cannot find function `socket_patched` in crate `cfg_if`
socket-patch vex --product pkg:cargo/[email protected] # statement status: not_affected
cargo clean -q -p cfg-if && cargo run -q --locked --offline # M:1, now patched
The vendor/ variant is the same, after cargo vendor vendor plus the [source.vendored-sources] directory = "vendor" replacement.
Rollback direction: apply → cargo run (M:1) → rollback (success, rolledBack 1) → cargo run still prints M:1.
Expected vs actual
- Expected: after a successful
apply, the next cargo build compiles the patched sources, and vex attests only what the build actually uses. At minimum, apply should invalidate the crate's build cache (for example, remove target/*/.fingerprint/<crate>-* and target/*/deps/lib<crate>-*), or loudly tell the user to run cargo clean -p <crate>. The contract (CLI_CONTRACT.md, Property 7) presents a byte-verified agent patch as a mitigation.
- Actual: cargo keeps the pre-apply rlib, apply stays silent, and VEX says
not_affected.
Matrix (all Linux, reproduced 2/2 on 1.93.1)
| cargo |
registry cache |
cargo vendor dir |
rollback direction |
| 1.56.1 |
fail |
fail |
not run |
| 1.84.1 |
fail |
fail |
not run |
| 1.93.1 |
fail |
fail |
fail |
| 1.97.0 (stable) |
fail |
fail |
not run |
| macOS / Windows |
not probed: cargo's fingerprint policy for non-path sources is platform-independent. The probe branch was skipped because the git proxy won't let this routine delete branches (HTTP 403) |
|
|
Suspect code
This is a design gap rather than one wrong line. The agent apply path (crates/socket-patch-core/src/patch/apply.rs plus patch/sidecars/cargo.rs, which only rewrites .cargo-checksum.json) never touches the build cache, and nothing in the cargo apply or rollback output mentions it. The .cargo-checksum.json rewrite doesn't help, because cargo checks it only when it (re)compiles the package.
Tested on main f6b7fb9 (CLI 4.0.0). It's inherent to in-place agent mode, so there's no first bad commit.
[agent] Found by the scheduled Cargo bug-hunt routine (ledger #315).
Summary
Agent mode patches a crate's sources in place, in
$CARGO_HOME/registry/src/<index>/<crate>-<ver>/or in acargo vendordirectory source. Cargo doesn't fingerprint registry or directory-source packages by file mtime or content. It treats them as immutable and keys their build cache on the package id. So if the project was built beforeapply(the normal case for a developer checkout, or a CI job with a cachedtarget/), the nextcargo build --lockedjust reuses the unpatchedlibcfg_if-*.rlibintarget/and never recompiles the patched sources.Meanwhile
applyreportssuccess/applied: 1, andvex(withsetup.manual: ["cargo"]) emitsnot_affected, because the on-disk file hashes do matchafterHash. The binary the user ships still contains the vulnerable code. Onlycargo clean -p <crate>(or a fullcargo clean) makes the patch take effect.rollbackhas the same problem in reverse: after apply → build → rollback, the next build still links the patched rlib.Vendored and hosted modes aren't affected, because they change the package's source id (path dependency or per-patch registry), which forces a rebuild.
Impact
It's a silent false fix, and VEX attests it. Any machine or CI cache that built the project before
applykeeps shipping the unpatched crate, with no warning anywhere. README / docs/ecosystems.md ("Cargo: shared registry cache") warn that the in-place patch is shared and reset bycargo clean, but say nothing about already-compiled artifacts ignoring the patch.Repro (Linux, any cargo)
The
vendor/variant is the same, aftercargo vendor vendorplus the[source.vendored-sources] directory = "vendor"replacement.Rollback direction: apply →
cargo run(M:1) →rollback(success, rolledBack 1) →cargo runstill printsM:1.Expected vs actual
apply, the nextcargo buildcompiles the patched sources, andvexattests only what the build actually uses. At minimum, apply should invalidate the crate's build cache (for example, removetarget/*/.fingerprint/<crate>-*andtarget/*/deps/lib<crate>-*), or loudly tell the user to runcargo clean -p <crate>. The contract (CLI_CONTRACT.md, Property 7) presents a byte-verified agent patch as a mitigation.not_affected.Matrix (all Linux, reproduced 2/2 on 1.93.1)
cargo vendordirSuspect code
This is a design gap rather than one wrong line. The agent apply path (
crates/socket-patch-core/src/patch/apply.rspluspatch/sidecars/cargo.rs, which only rewrites.cargo-checksum.json) never touches the build cache, and nothing in the cargo apply or rollback output mentions it. The.cargo-checksum.jsonrewrite doesn't help, because cargo checks it only when it (re)compiles the package.Tested on main
f6b7fb9(CLI 4.0.0). It's inherent to in-place agent mode, so there's no first bad commit.