Fix gem VEX ignoring out-of-tree bundle path (#709) - #712
Mikola Lysenko (mikolalysenko) wants to merge 5 commits into
Conversation
Assisted-by: Claude Code:claude-opus-5-5
A .bundle/config "path" outside the project (bundle config set --local path /opt/bundle) is refused as an install root because apply writes there. That refusal also hid the root from the read-only checks, so hosted scan gave no stale-install warning and both the in-run --vex and a later `vex` attested not_affected while bundler kept loading the unpatched gem from that path. The refused root is now exposed as a verification-only store: the hosted stale-install probe and vex's installed-copy lookup read it, while apply and rollback still never write there. A stale copy there gets the project-local remedy. Fixes #709 Assisted-by: Claude Code:claude-opus-5-5
Covers #709 end to end: a stale gem under a .bundle/config path outside the project now warns with the project-local remedy, and the same run's --vex does not attest it. Assisted-by: Claude Code:claude-opus-5-5
The regression test for #709 was nested inside another test function, so it compiled but never ran. Move it back to module level. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
|
[agent] Generated by Claude Code |
With BUNDLE_IGNORE_CONFIG set, bundler reads no config file, so a .bundle/config path is neither an install root nor a root bundler loads from. Discovery now skips the app config's BUNDLE_PATH in that case, the same way the cache-path and Gemfile readers already do, so leftover gems under that path no longer raise a stale-install warning or fail VEX. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit c8d4d3a. Configure here.
|
[burn-down agent] Ready for review at head
Slack announcement not sent: this run has no Slack send tool. Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #709
Summary
If
.bundle/configsets a bundlepathoutside the project (bundle config set --local path /opt/bundle,~/.bundle-store, or a WindowsD:\...path), hosted gem verification used to treat the gem as not installed.scan --mode hostedgave no stale-install warning, and both the in-run--vexand a latervexmarked the advisorynot_affected, even though Bundler kept loading the unpatched gem from that path. This PR makes both checks read that path.applyandrollbackstill never write there.Root cause
The ruby crawler's containment guard (
resolve_config_bundle_path) refuses a config-sourcedBUNDLE_PATHthat resolves outside the project. That's deliberate: crawled roots are apply write targets, and a committed.bundle/configis untrusted input. But the refusal also hid the root from the two read-only consumers whose job is to catch an unpatched install:gem_stale_install_warnings), so noredirect_gem_stale_installwas raised and the purl stayed in the in-run--vexassume_appliedset;vex's installed-copy lookup (find_manifest_package_copies_reusing), so the gem came backpackage_not_foundand the hosted lockfile-basis excuse (vex.rs~L581) marked itnot_affected.Fix
ruby_crawler.rs):BundleStoreDiscoverynow also records the resolved root it refused (skipped_config_root). NewRubyCrawler::verification_only_gem_paths[_with_env]returns the gem stores under that root, for local mode only. They are deliberately never part ofget_gem_paths, which is what apply and rollback write through.scan/hosted.rs): also reads those stores. A stale copy there gets the project-local delete-list remedy, plus the committedvendor/cachearchive fold-in, instead of the shared-gem-home caveat.gem_stale_install_warningnow takesproject_local: boolinstead of deriving it fromcwd.ecosystem_dispatch.rs):find_manifest_package_copies_reusingis called only byvex, which only reads. It now adds gem copies found under those stores. An unpatched copy fails verification (not_applied/hash_mismatch) and is no longer excused as absent. A patched copy there is accepted as patched.CLI_CONTRACT.md: the config-skip and "Gem stale-install guard" sections now document the read-only behavior.The npm/pypi/gem wrappers only dispatch to the binary, so they need no changes.
Tests (each new test was seen failing before the fix and passing after)
scan::hosted::tests::gem_stale_probe_reads_refused_out_of_tree_config_pathvexfalsenot_affectede2e_vex_redirect::gem_hosted_ref_is_verified_under_an_out_of_tree_config_bundle_pathverified) → green (unpatched: exit 1; patched: attests; nothing installed: lockfile basis still attests)scan --mode hosted --vexfalsenot_affectede2e_redirect_gem_stale_install::gem_hosted_stale_install_under_out_of_tree_config_path_is_not_attested~spelling; env dedup; global mode;BUNDLE_IGNORE_CONFIG(Bugbot)ruby_crawler::tests::refused_config_root_is_verification_onlyLocal runs:
cargo clippy --workspace --all-features -- -D warnings: clean.cargo test --workspace --all-features --no-fail-fast: all pass except 12 permission-based write-failure tests (vendor/repair/copy_tree/vlt_heal/pypi). Those rely onchmodmaking files read-only, which root ignores in this sandbox. They are unrelated to this diff and pass in CI.cargo fmt:mainitself isn't rustfmt-clean with the pinned 1.93.1, so I only formatted the lines I changed.Note
Medium Risk
Changes gem VEX and hosted stale-install behavior (attestation-sensitive) but leaves apply/rollback write containment unchanged; scope is limited to read-only Ruby crawler paths.
Overview
Fixes #709: when
.bundle/configpointsBUNDLE_PATHoutside the project, the crawler still refuses that root for apply/rollback writes (untrusted committed config), but read-only checks now probe it because Bundler installs and loads gems there anyway.Core:
RubyCrawlerrecords the refused root asskipped_config_rootand exposes it viaverification_only_gem_paths(not part ofget_gem_paths).BUNDLE_IGNORE_CONFIGskips this extra probe.Hosted
scan: The gem stale-install guard unions those stores into discovery; installs under the project’s configured bundle path get the project-local delete-list remedy even when the path sits outside--cwd.gem_stale_install_warningtakes an explicitproject_localflag.vex:find_manifest_package_copies_reusingalso finds gem copies under verification-only stores so unpatched installs fail verification instead of being treated as absent (and falsely attested via lockfile basis); stale purls stay out of in-run--vexassume_applied.Docs + tests:
CLI_CONTRACT.mddocuments write vs read behavior; unit and e2e tests cover stale warnings, VEX, and verification-only path rules.Reviewed by Cursor Bugbot for commit c8d4d3a. Configure here.