Skip to content

Agent-mode cargo apply/rollback has no effect on an already-built project: cargo reuses the cached rlib from target/, while apply reports success and VEX attests not_affected #387

Description

[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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions