[agent] Found by the scheduled Cargo bug-hunt routine (ledger #315).
Summary
When HOME is unset and CARGO_HOME is unset, cargo still finds its home: the home crate falls back to the passwd entry (getpwuid(getuid())->pw_dir), so it builds from /root/.cargo (or /home/<user>/.cargo). socket-patch's cargo crawler resolves CARGO_HOME, then utils::fs::home_dir(), which since #1038 reads only HOME / USERPROFILE and returns None otherwise, so it probes no registry at all. An agent scan --mode agent then sees the patched crate only in Cargo.lock (lockfileOnlyPackages: 1), patches nothing, and exits 0 with status: "success". The next cargo build compiles the unpatched registry copy.
Impact
Repro (Linux, cargo 1.93.1, root user, passwd home /root)
mkdir -p proj/src && cd proj
printf '[package]\nname = "consumer"\nversion = "0.1.0"\nedition = "2018"\n\n[dependencies]\ncfg-if = "1.0.4"\n' > Cargo.toml
echo 'fn main() {}' > src/main.rs
cargo generate-lockfile && cargo update -p cfg-if --precise 1.0.4 && cargo fetch
env -u HOME -u CARGO_HOME cargo metadata --format-version 1 | jq -r '.packages[] | select(.name=="cfg-if").manifest_path'
# /root/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/cfg-if-1.0.4/Cargo.toml <- cargo still uses ~/.cargo
# patch API stand-in serving one free patch for pkg:cargo/cfg-if@1.0.4 (appends `pub fn socket_patched()` to src/lib.rs)
env -u HOME -u CARGO_HOME socket-patch scan --mode agent --json --yes --api-url $MOCK --org test-org --api-token fake
# status: success, scannedPackages: 1, lockfileOnlyPackages: 1, totalPatches: 1, events: [], exit 0
grep -c socket_patched /root/.cargo/registry/src/*/cfg-if-1.0.4/src/lib.rs # 0: not patched
# control, same project, HOME=/root:
socket-patch scan --mode agent --json --yes ... # scannedPackages: 233, lockfileOnlyPackages: 0, the copy is patched
It reproduced on two consecutive runs (identical output), and the control with HOME set patches the copy every time.
Expected vs actual
- Expected: agent mode patches "the crate wherever the crawler finds it" (docs/ecosystems.md, Cargo row) in the registry cache cargo actually builds from. When
CARGO_HOME is unset, that's the default cargo home, <home>/.cargo, with <home> resolved the way cargo resolves it (HOME, then the passwd entry on Unix). utils::fs::home_dir's own doc already lists readers that "deliberately follow another tool's own rule" (Gradle's passwd-vs-$HOME check in gradle/home.rs), so cargo needs the same treatment. Failing that, the run should at least say that no cargo home was found, instead of passing as lockfile-only.
- Actual: no home, so no registry probe, the crate is reported lockfile-only,
status: success, exit 0, and the build is unpatched.
Matrix
| OS |
cargo |
HOME |
CARGO_HOME |
Reproduces |
| Linux |
1.93.1 |
unset |
unset |
yes (×2) |
| Linux |
1.93.1 |
/root |
unset |
no (control) |
| Linux |
1.93.1 |
unset |
set |
no (the crawler uses CARGO_HOME) |
| macOS |
any |
unset |
unset |
untested (same code path; cargo's passwd fallback is the same) |
| Windows |
any |
USERPROFILE unset |
unset |
untested (cargo's home crate uses the known-folder API there) |
Tested on main 9472be4 (CLI 4.0.0). Not a regression from #1038: before it, home_dir fell back to the CWD-relative path ~ (that commit's message), which also found no registry. #1038 just made "no home" explicit.
Suspect code
crates/socket-patch-core/src/crawlers/cargo_crawler.rs:355-360 (CargoCrawler::cargo_home): CARGO_HOME, else utils::fs::home_dir().map(|h| h.join(".cargo")), with no passwd fallback.
crates/socket-patch-core/src/utils/fs.rs:387-398 (home_dir / home_from_env): HOME / USERPROFILE only.
- The same
CARGO_HOME-or-home_dir rule is in crates/socket-patch-core/src/vendor/cargo_config.rs:156-158 (read_config_chain). With HOME unset, the $CARGO_HOME/config.toml that cargo still reads (a user [patch] / [source]) is skipped by the vendored and hosted config-chain checks too. I didn't drive that path end to end.
Related: #616 (the same lockfile-only exit 0 when the cache is genuinely cold). A fix there that makes a lockfile-only cargo patch loud would surface this case, but it still wouldn't patch the copy cargo builds.
[agent] Found by the scheduled Cargo bug-hunt routine (ledger #315).
Summary
When
HOMEis unset andCARGO_HOMEis unset, cargo still finds its home: thehomecrate falls back to the passwd entry (getpwuid(getuid())->pw_dir), so it builds from/root/.cargo(or/home/<user>/.cargo). socket-patch's cargo crawler resolvesCARGO_HOME, thenutils::fs::home_dir(), which since #1038 reads onlyHOME/USERPROFILEand returnsNoneotherwise, so it probes no registry at all. An agentscan --mode agentthen sees the patched crate only inCargo.lock(lockfileOnlyPackages: 1), patches nothing, and exits 0 withstatus: "success". The nextcargo buildcompiles the unpatched registry copy.Impact
env -iscript or container entrypoint that runs withoutHOMEgets a green socket-patch step and an unpatched build.vexexits 2 in the same environment, so the gap shows up only if someone runs vex too.Repro (Linux, cargo 1.93.1, root user, passwd home
/root)It reproduced on two consecutive runs (identical output), and the control with
HOMEset patches the copy every time.Expected vs actual
CARGO_HOMEis unset, that's the default cargo home,<home>/.cargo, with<home>resolved the way cargo resolves it (HOME, then the passwd entry on Unix).utils::fs::home_dir's own doc already lists readers that "deliberately follow another tool's own rule" (Gradle's passwd-vs-$HOMEcheck ingradle/home.rs), so cargo needs the same treatment. Failing that, the run should at least say that no cargo home was found, instead of passing as lockfile-only.status: success, exit 0, and the build is unpatched.Matrix
/rootCARGO_HOME)USERPROFILEunsethomecrate uses the known-folder API there)Tested on main
9472be4(CLI 4.0.0). Not a regression from #1038: before it,home_dirfell back to the CWD-relative path~(that commit's message), which also found no registry. #1038 just made "no home" explicit.Suspect code
crates/socket-patch-core/src/crawlers/cargo_crawler.rs:355-360(CargoCrawler::cargo_home):CARGO_HOME, elseutils::fs::home_dir().map(|h| h.join(".cargo")), with no passwd fallback.crates/socket-patch-core/src/utils/fs.rs:387-398(home_dir/home_from_env):HOME/USERPROFILEonly.CARGO_HOME-or-home_dirrule is incrates/socket-patch-core/src/vendor/cargo_config.rs:156-158(read_config_chain). WithHOMEunset, the$CARGO_HOME/config.tomlthat cargo still reads (a user[patch]/[source]) is skipped by the vendored and hosted config-chain checks too. I didn't drive that path end to end.Related: #616 (the same lockfile-only exit 0 when the cache is genuinely cold). A fix there that makes a lockfile-only cargo patch loud would surface this case, but it still wouldn't patch the copy cargo builds.