Repository navigation
Gem crawler ignores Bundler's path.system: true when a leftover vendor/bundle exists, so agent apply patches the unused copy and vex attests not_affected while Bundler loads the unpatched system gem #915
Description
[agent] Found by the scheduled Bundler (RubyGems) bug-hunt routine (ledger #316).
Summary
bundle config set --local path.system true (or BUNDLE_PATH__SYSTEM=true in the environment) is Bundler's documented way to stop using a bundle path and go back to system gems. A project that used to install into vendor/bundle usually still has that directory, since it's gitignored and nobody deletes it. In that state:
- Bundler installs into and loads from the system gem home (
Gem.loaded_specs[...].full_gem_path=/…/lib/ruby/gems/3.3.0/gems/colorize-0.8.1). - The gem crawler always probes the default
<cwd>/vendor/bundle. Because that root holds stores, it skips thegem envhomes (ruby_crawler.rs:132). - Agent
applytherefore patches only the stalevendor/bundlecopy, reportssuccess, andsocket-patch vexemitsnot_affectedfor the CVE while the app runs the unpatched system copy.
The crawler already parses BUNDLE_PATH__SYSTEM (parse_bundle_config_path, and CLI_CONTRACT.md says it "drops the recorded path, as bundler itself ignores it"). But it only drops the recorded path. It keeps the default vendor/bundle root, which Bundler ignores just as much, and lets that root suppress the system homes. The env form only drops the global-config tier (global_path_config_unless_env_path_settings).
This is narrower than the plain "leftover vendor/bundle, no config at all" heuristic that CLI_CONTRACT.md documents ("When the default vendor/bundle root holds no store, the gem homes gem env reports are appended"). Here Bundler has been told explicitly that the bundle path is not in use, and the crawler reads that setting but doesn't act on it.
Impact
A false VEX attestation: not_affected / inline_mitigations_already_exist for a vulnerability whose code is still the one being loaded. Agent apply also reports success without patching the copy that runs.
Repro (Linux, Ruby 3.3.6, Bundler 4.0.22; no network beyond rubygems.org)
mkdir app && cd app && git init -q
printf 'source "https://rubygems.org"\ngem "colorize", "0.8.1"\n' > Gemfile
bundle config set --local path vendor/bundle && bundle install # old setup
bundle config unset --local path
bundle config set --local path.system true && bundle install # back to system gems
# hand-written .socket/manifest.json + blobs: pkg:gem/colorize@0.8.1,
# package/lib/colorize.rb beforeHash = upstream, afterHash = upstream + "$SOCKET_PATCHED_COLORIZE = true"
socket-patch apply --offline --json # status success, 1 applied
bundle exec ruby -e 'require "colorize"; p defined?($SOCKET_PATCHED_COLORIZE) ? :PATCHED : :UNPATCHED; puts Gem.loaded_specs["colorize"].full_gem_path'
# => :UNPATCHED /opt/rbenv/versions/3.3.6/lib/ruby/gems/3.3.0/gems/colorize-0.8.1
tail -1 vendor/bundle/ruby/3.3.0/gems/colorize-0.8.1/lib/colorize.rb # => $SOCKET_PATCHED_COLORIZE = true (the unused copy)
socket-patch vex --product pkg:gem/app@1.0.0 # exit 0, "status":"not_affected"The env variant is the same with export BUNDLE_PATH__SYSTEM=true instead of the local config.
Expected vs actual
- Expected: CLI_CONTRACT.md ("Gem install roots") says the crawler probes "the project's Bundler install roots in bundler's own precedence order", and that
BUNDLE_PATH__SYSTEM: "true"is honored "as bundler itself ignores it". Withpath.systemtrue in the winning tier (Bundler::Settings#path→Path#use_system_gems?istrue), Bundler's install root is thegem envhome. Soapplyshould patch that copy (or at least also patch it), andvexshould verify the copy Bundler loads and refuse withnot_appliedwhile it's unpatched. - Actual: only
vendor/bundleis crawled.applysucceeds on the unused copy, the loaded copy stays unpatched, andvexattestsnot_affected.
Matrix (agent mode, Linux, Ruby 3.3.6, main 9c43dfc)
| Bundler | local path.system: true + leftover vendor/bundle |
env BUNDLE_PATH__SYSTEM=true + leftover vendor/bundle |
control: local path.system: true, no vendor/bundle |
|---|---|---|---|
| 4.0.22 | fail (2/2) | fail (2/2) | pass (system copy patched, vex correct) |
| 2.6.9 | fail | fail | pass |
| 2.4.22 | fail | fail | — |
The logic is OS-independent (pure path selection), so I didn't run macOS/Windows probes. Published v4.0.0 also patches only vendor/bundle. Its vex omits the patch for an unrelated reason (the old setup gating), so the false attestation shows up on v5 main.
Suspect code
crates/socket-patch-core/src/crawlers/ruby_crawler.rs:417:roots.push(default_root.clone())runs unconditionally, even when the effective Bundler tier setspath.systemtrue.crates/socket-patch-core/src/crawlers/ruby_crawler.rs:132:!discovery.default_root_has_storesthen suppresses thegem envhomes, the only place the loaded copy lives.crates/socket-patch-core/src/crawlers/ruby_crawler.rs:1474(parse_bundle_config_path):path.systemonly drops the recorded path. The envBUNDLE_PATH__SYSTEMonly drops the global tier (global_path_config_unless_env_path_settings,:1293).
Activity
bundler: run 23 ledger (#915)
mikolalysenko commentedon Oct 6, 2026
[agent] Triaged as priority:p1 (Bundler). Confirmed on main
9c43dfc:ruby_crawler.rspushes the defaultvendor/bundleroot unconditionally (~L417) even when the winning Bundler tier setspath.system, anddefault_root_has_storesthen suppresses thegem envhomes (~L132). Not a duplicate: #729 is about the hosted stale-install warning text, not crawl roots. No open PR covers it yet.Generated by Claude Code
mikolalysenko commentedon Oct 6, 2026
[agent] Claiming this issue (single-issue cluster; root cause: the gem crawler always counts the default
vendor/bundleroot even when the winning Bundler tier setspath.system: true). Branch: agent/fix-gem-path-system-default-root. Claim-ID: 2026-10-06T04:20:47Z-aa3829Generated by Claude Code
mikolalysenko commentedon Oct 6, 2026
[agent] Draft PR: #916
Generated by Claude Code
Patch the gem Bundler loads under path.system
mikolalysenko commentedon Oct 6, 2026
[agent] Shares root cause with #967: the gem crawler doesn't model Bundler's
Settings#path/Path#use_system_gems?resolution. This issue is thesystem_pathhalf (#916); #967 is the.bundlebase-path half, which #968 fixes, stacked on #916.Generated by Claude Code
Fix gem crawler missing Bundler .bundle root (#967) (#968)