Skip to content

Gem VEX judges an unused system gem-home copy when the project sets a Bundler path, so standalone vex never attests #1098

Description

[agent] Filed by the scheduled architecture audit routine (ecosystems and formats). Register: register comment.

Kind: bug (inconsistent logic within one ecosystem). Source: new finding; register E90.

Problem

The question "which gem homes does Bundler load this project's gems from" now has two answers on main @ e61a845:

  1. RubyCrawler::get_gem_paths (ruby_crawler.rs#L43-L58). It appends every `gem env` home whenever the default `vendor/bundle` holds no store ([`#L136-L143`](https://github.com/SocketDev/socket-patch/blob/e61a8458651f78a8d42e4aad437e66a9f9894f6b/crates/socket-patch-core/src/crawlers/ruby_crawler.rs#L136-L143)).`` It does this even when the project sets an explicit Bundler path, where Bundler disables shared gems. This list feeds scan discovery, agent apply, and vex's installed-copy lookup (ecosystem_dispatch.rs#L618-L660).``
  2. RubyCrawler::bundler_install_homes (#L645-L690).`` It was added by Fix gem stale-install guard home selection (#1001, #729) #1002 for Hosted gem stale-install guard flags an unused system gem-home copy when the project sets a Bundler path that isn't installed yet, so scan --mode hosted --vex fails with no_applicable_patches on fresh checkouts #1001 and counts the gem env homes only when Bundler uses system gems: no deployment store, and no explicit `path` according to `bundler_sets_explicit_path`. Only the hosted stale-install guard uses it (`scan/hosted.rs#L379`).

For a hosted gem purl, vex requires every copy in list 1 to verify (vex_consumed.rs#L28, vex.rs#L624-L630). #1002 fixed the #1001 symptom for scan --mode hosted --vex, but the standalone vex command still has it.

Proved by execution. I ran a throwaway test twice on e61a845. It reuses the harness of e2e_redirect_gem_stale_install.rs: stage_system_home_copy provides a fake gem env gemdir home holding the unpatched stale-probe-gem-1.0.0, plus the mock API. The steps were:

case standalone vex
fresh checkout, BUNDLE_PATH: vendor/bundle (nothing installed yet) exit 1, not_applied ("the patched files still hold the original content"), no_applicable_patches
installed: BUNDLE_PATH: gems, with the patched copy in gems/ruby/3.3.0/gems/ exit 1, not_applied
control: same as the row above, with the system-home copy removed exit 0, verified, not_affected

In both failing cases, Bundler never loads the system-home copy (the premise of #1001 and #1002). The project is patched, or about to be, yet vex refuses it on every run. On the fresh checkout, the unused copy also prevents the hosted lockfile basis from applying, because the copy counts as "installed".

The agent apply path reads the same list 1. In an explicit-path project, it therefore also writes to (and rollback restores) a same-version copy in the shared gem home, which Bundler doesn't load for that project. I found this by reading the code; I didn't execute it.

Symptoms

Impact: fail-closed (no false attestation), but a false negative. Any developer or CI machine whose system gem home holds an old copy of a patched gem never gets vex output for that gem, in projects that set path (common in CI caches, bundle config set --local path vendor/bundle). Size: one model function plus three call sites.

Proposed change

  • Make bundler_install_homes (or one BundlerHomes { loaded, default_gem_homes } value built once) the single answer to "which homes does Bundler load for this project".
  • Make the gem branch of the vex installed lookup (find_manifest_package_copies_reusing and hosted_consumed_copies) judge only those homes.
  • Leave get_gem_paths with only its documented default-gem reason for keeping the gem env homes under an explicit path. Either restrict those homes to gems whose spec is under specifications/default/, or keep them for apply only. The PR decides which and documents it in CLI_CONTRACT's gem section.
  • Delete the duplicated tier read: bundler_install_homes re-reads the app, env and global tiers that discover_bundle_stores_impl has already resolved. BundleStoreDiscovery should carry explicit_path so the rule lives in one place.

Size and scope

Acceptance criteria

Dependencies


Backlog review — 2026-10-08

Priority: P1 → P2. An unused system gem copy makes VEX reject a correctly patched Bundler path; false negative.

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

    agent:triagedarch-auditFiled by a scheduled architecture audit routine (see the architecture review discussion)bugSomething isn't workingpm:bundlerBundler (RubyGems)priority:p2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions