Repository navigation
Gem crawler ignores Bundler's .bundle default install path (default_install_uses_path on 2.x, simulate_version 5 on 4.x), so agent apply patches the system copy and vex attests not_affected while Bundler loads the unpatched .bundle/ruby/<abi> copy #967
Description
[agent] Found by the scheduled Bundler (RubyGems) bug-hunt routine (ledger #316).
Summary
Bundler has a documented setting that switches the default install location from the system gem home to <root>/.bundle:
- Bundler 2.x:
default_install_uses_path(BUNDLE_DEFAULT_INSTALL_USES_PATH). Frombundle-config(1): "Whether abundle installwithout an explicit--pathargument defaults to installing gems in.bundle." - Bundler 4.x:
simulate_version 5(BUNDLE_SIMULATE_VERSION=5), the documented way to opt into Bundler 5 defaults. In 4.0.22,Settings::Path#use_system_gems?returns!Bundler.feature_flag.bundler_5_mode?when nopathis set, andbase_pathbecomes".bundle". This is also the planned default for Bundler 5.
With either setting, bundle install installs into <root>/.bundle/ruby/3.3.0/gems/<gem>, and bundle exec loads from there. The gem crawler's install-root discovery (config path, env BUNDLE_PATH, standalone ./bundle, default vendor/bundle, then the gem env homes) never probes <root>/.bundle. So agent apply patches the system copy (exit 0, success), and vex emits not_affected for a CVE that is still live in the copy the app actually loads.
Impact
A silent false negative plus a false VEX attestation. It also writes to the machine-wide gem home, which the project doesn't use. Once Bundler 5 ships with this default, every plain bundle install project is affected.
Repro (Linux, Ruby 3.3.6, no network beyond rubygems.org, hand-written manifest + apply --offline)
gem install colorize -v 0.8.1 # a system copy also exists (common on CI images / dev machines)
mkdir proj && cd proj
printf 'source "https://rubygems.org"\ngem "colorize", "0.8.1"\n' > Gemfile
bundle _4.0.22_ config set --local simulate_version 5 # or: bundle _2.6.9_ config set --local default_install_uses_path true
bundle _4.0.22_ install
bundle _4.0.22_ exec ruby -e 'puts Gem.loaded_specs["colorize"].full_gem_path'
# => /tmp/…/proj/.bundle/ruby/3.3.0/gems/colorize-0.8.1
# .socket/manifest.json: pkg:gem/colorize@0.8.1, package/lib/colorize.rb before/after git-sha256 + blob (marker line appended)
socket-patch apply --offline --json # => status "success"
socket-patch vex --product pkg:gem/app@1.0.0 -O vex.json # => exit 0, statement status not_affected
bundle exec ruby -e 'print File.read(Gem.loaded_specs["colorize"].full_gem_path+"/lib/colorize.rb").include?("SOCKET-PATCHED")'
# => false (the loaded .bundle copy is unpatched)
grep -c SOCKET-PATCHED "$(gem contents colorize -v 0.8.1 | grep lib/colorize.rb$)"
# => 1 (the system copy, which the project doesn't load, was patched)Expected vs actual
- Expected:
crates/socket-patch-cli/CLI_CONTRACT.md("Gem install roots") says the crawler probes "the project's Bundler install roots in bundler's own precedence order". When Bundler resolves no explicitpathbutdefault_install_uses_path/bundler_5_mode?is on, its install root is<root>/.bundle(scoped<root>/.bundle/<engine>/<abi>/gems/), so that root should be probed, and thegem envhomes shouldn't be the patch target. Failing that,vexmust not attestnot_affectedwhile that copy is unpatched. - Actual:
<root>/.bundle/ruby/<abi>/gemsis never probed. The system copy is patched andvexattests.
OS × version matrix
| OS | Ruby | Bundler | Setting | Result |
|---|---|---|---|---|
| Linux | 3.3.6 | 4.0.22 | local simulate_version 5 |
fail (×2) |
| Linux | 3.3.6 | 4.0.22 | env BUNDLE_SIMULATE_VERSION=5 |
fail |
| Linux | 3.3.6 | 2.6.9 | local default_install_uses_path true |
fail (×2) |
| Linux | 3.3.6 | 2.6.9 | env BUNDLE_DEFAULT_INSTALL_USES_PATH=true |
fail |
| Linux | 3.3.6 | 2.5.22 | local default_install_uses_path true |
fail |
| Linux | 3.3.6 | 4.0.22 | no setting (control: system install) | pass (loaded copy patched) |
The logic is OS-independent (pure path discovery), so no macOS/Windows probe was run. It isn't a regression: the crawler never modeled this root.
Suspect code
crates/socket-patch-core/src/crawlers/ruby_crawler.rs:354(discover_bundle_stores_impl): the roots list is configpath→ envBUNDLE_PATH→./bundle(standalone) → global configpath→vendor/bundle. There's no<cwd>/.bundleroot whenBUNDLE_DEFAULT_INSTALL_USES_PATH(2.x) orBUNDLE_SIMULATE_VERSION >= 5(4.x) is set in the app config, env or global config and no tier setspath/path.system/disable_shared_gems.- The hosted stale-install guard reuses the same discovery, so it probably misses a stale
.bundlematerialization too (not verified end-to-end in this run).
Tested on main 9c43dfc.
Activity
mikolalysenko commentedon Oct 6, 2026
[agent] Claiming this issue (shared root cause with #915, in flight in #916: the gem crawler does not mirror Bundler's
Settings#path/Path#use_system_gems?resolution, so a.bundlebase path is never probed). Branch: agent/fix-gem-dot-bundle-default-root. Claim-ID: 2026-10-06T22:20:32Z-2b2a7fGenerated by Claude Code
mikolalysenko commentedon Oct 6, 2026
[agent] Draft PR: #968 (stacked on #916, which covers the
path.systemhalf of the same Bundler path-resolution gap, #915).Generated by Claude Code
Patch gems Bundler installs into .bundle
mikolalysenko commentedon Oct 7, 2026
[agent] Re-triage from the Bundler bug-hunt routine (ledger #316): I checked PR #968 at head
25e3ca6against the original repro (Linux, Ruby 3.3.6, hand-writtencolorize@0.8.1manifest,apply --offline, thenvex, then a realbundle exec).9c43dfc: loaded.bundlecopy patched.bundlecopy patchedsimulate_version 5BUNDLE_SIMULATE_VERSION=5default_install_uses_path trueBUNDLE_DEFAULT_INSTALL_USES_PATH=trueWith PR #968, the copy Bundler loads gets patched, so the
not_affectedVEX now matches what runs. The system copy is still patched too. That's consistent with the PR's comment that thegem envfallback stays on because default gems live in the system homes, so I'm not treating it as a gap. On my side, this issue is fixed by #968 once it merges.Generated by Claude Code
mikolalysenko commentedon Oct 7, 2026
[agent] More from the Bundler bug-hunt routine (ledger #316): I've now verified the hosted stale-install half end-to-end. It was only suspected in the issue body.
Setup: Linux, Ruby 3.3.6, Bundler 4.0.22, local
simulate_version 5,gem "colorize", "~> 0.8.1". The unpatched install is kept under.bundle/ruby/3.3.0/gems/colorize-0.8.1, and there's no system copy. Thenscan --mode hosted --vex …(mock patch API + registry, real rubygems.org upstream), followed by a frozen install of the rewritten files plus.bundlein a fresh copy.9c43dfcnot_affected(exit 0).bundle/ruby/3.3.0/gems/colorize-0.8.1, unpatched25e3ca6redirect_gem_stale_installfor the.bundlecopyno_applicable_patches)So on main, the hosted in-run VEX also attests a CVE that's still live. PR #968 fixes that too. With
--cwdleft at its default, the PR's warning calls.bundle/…a "shared gem home"; that wording is #729.Generated by Claude Code
Merge main to pick up the landed #916 path.system fix