Skip to content

Agent-mode cargo patches the unused registry copy of a crate the user overrides with [patch.crates-io], and VEX attests not_affected while the build links the user's unpatched fork #506

Description

[agent] Found by the scheduled Cargo bug-hunt routine (ledger #315).

Summary

The project's root Cargo.toml overrides a crate with [patch.crates-io] cfg-if = { path = "local/cfg-if" }, and Cargo.lock records that crate with no source, because it resolves to the local path. A copy of cfg-if-1.0.4 from crates.io is still extracted under $CARGO_HOME/registry/src (from an earlier build, or from any other project on the machine).

scan --mode agent matches pkg:cargo/cfg-if@1.0.4 against that registry-cache copy, patches it, records the patch and reports applied: 1. Cargo never builds that copy: cargo build --locked --offline compiles the user's local/cfg-if. socket-patch vex --product … then verifies the patched registry copy and emits a not_affected / inline_mitigations_already_exist statement for pkg:cargo/cfg-if@1.0.4. The code that actually ships is the user's fork, which socket-patch never looked at.

Impact

Repro

This uses a local stand-in for the patch API (--api-url, serving /v0/orgs/<org>/patches/{batch,by-package,view} with one patch that appends pub fn socket_patched() to cfg-if-1.0.4/src/lib.rs, plus one GHSA). It's the same fixture shape as tests/in_process_agent_reapply.rs.

export CARGO_HOME=$PWD/home
cargo new -q --bin proj && cd proj
printf 'cfg-if = "=1.0.4"\n' >> Cargo.toml
cargo build -q                       # extracts registry/src/*/cfg-if-1.0.4
mkdir -p local/cfg-if/src
printf '[package]\nname = "cfg-if"\nversion = "1.0.4"\nedition = "2018"\n' > local/cfg-if/Cargo.toml
echo 'pub fn user_fork() -> u32 { 7 }' > local/cfg-if/src/lib.rs
printf '\n[patch.crates-io]\ncfg-if = { path = "local/cfg-if" }\n' >> Cargo.toml
cargo build -q && rm -rf target       # Cargo.lock: [[package]] name = "cfg-if" version = "1.0.4"  (no source)

A="--api-url http://127.0.0.1:18767 --org test-org --api-token fake --no-telemetry"
socket-patch scan --mode agent --json --yes $A      # status: success, applied: 1
tail -1 $CARGO_HOME/registry/src/*/cfg-if-1.0.4/src/lib.rs   # pub fn socket_patched() -> u32 { 1 }   (patched, but unused)

echo 'fn main(){ println!("{}", cfg_if::user_fork()); }' > src/main.rs
cargo run -q --locked --offline       # 7: the build links local/cfg-if, the unpatched fork

socket-patch vex --product pkg:cargo/consumer@0.1.0 --output doc.json $A --patch-server-url http://127.0.0.1:18767
# Wrote OpenVEX document with 1 statement
# statement: not_affected / inline_mitigations_already_exist, subcomponent pkg:cargo/cfg-if@1.0.4, GHSA-test-cfgif

Expected vs actual

  • Expected: a crate whose Cargo.lock entry has no registry source (a [patch] / path resolution) isn't the crates.io package pkg:cargo/cfg-if@1.0.4 that the build consumes. Agent mode should skip it with a warning, or at least vex must not attest it. CLI_CONTRACT.md says vex attests an agent-mode patch when "verification finds it applied", and verification has to look at the copy the build consumes. That's the rule spelled out for hosted references in the same table ("The installed copies the build consumes … are hash-verified"). Vendored mode refuses the same project with user_authored_patch_entry (docs/ecosystems.md, "Your entries").
  • Actual: applied: 1, no warning, and a not_affected statement for a crate whose built code was never patched.

Matrix

OS cargo Agent + user [patch.crates-io] path override of the patched crate
Linux 1.93.1 (repo toolchain) fail (3/3)
Linux 1.97.0 (stable) fail (1/1)
macOS / Windows any untested (crawler and VEX logic are OS-independent)

Tested on main 61cfb9b. Not bisected. The crawler fallback has looked like this since before v5.

Suspect code

  • crates/socket-patch-core/src/crawlers/cargo_crawler.rs:136-190 (get_crate_source_paths) and :219 (find_by_purls): in local mode the crawler falls back to every $CARGO_HOME/registry/src/<index> dir and matches by <name>-<version> only. It never checks that Cargo.lock resolves that name@version to the crates.io registry, rather than leaving it sourceless because of a [patch] path override.
  • The agent-mode VEX verification reuses that crawler result, so it hash-verifies the unused registry copy.

Related: the ledger's open maintainer question on agent scope versus Cargo.lock. This case is narrower: the crate is in Cargo.lock, but resolved to a different source. Also related: #480 (the hosted twin) and #501 (the same "patched the shadowed copy, VEX attests" shape in Python).

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