[agent] Found by the scheduled Bundler (RubyGems) bug-hunt routine (ledger #316).
Summary
Multi-Gemfile projects (the Appraisal / gemfiles/*.gemfile CI-matrix layout) run Bundler with BUNDLE_GEMFILE=gemfiles/rails_7.gemfile. That moves Bundler.root to gemfiles/, which has two effects:
- the app config Bundler reads is
gemfiles/.bundle/config (Bundler.app_config_path = root.join(".bundle"));
- a relative bundle path, whether from that config or from the
BUNDLE_PATH env var, resolves against gemfiles/. bundle install with path vendor/bundle therefore installs into gemfiles/vendor/bundle/ruby/<abi>.
The agent-mode crawler always uses the --cwd root instead. It reads <cwd>/.bundle/config, resolves BUNDLE_PATH against <cwd>, and probes <cwd>/vendor/bundle. The code's own doc comment says a relative value resolves "against the directory of the Gemfile (Bundler.root), not the process cwd" (ruby_crawler.rs:1391). It finds no store, falls back to the gem env homes, and patches the system copy. vex then attests not_affected while bundle exec loads the unpatched gemfiles/vendor/bundle copy. Nothing warns.
Hosted and vendored modes already treat a BUNDLE_GEMFILE outside the root pair as unsupported (redirect_gem_bundle_gemfile_unsupported / gemfile_not_loaded). Agent mode and vex don't consult it at all.
Impact
Silent: the patch lands in a copy the app doesn't load, and the VEX document claims the vulnerability is mitigated. This shape is the standard GitHub Actions matrix, env: BUNDLE_GEMFILE: gemfiles/<x>.gemfile. A relative BUNDLE_PATH: vendor/bundle export, or bundle config set --local path vendor/bundle run under that env, both land in gemfiles/vendor/bundle.
Repro (Linux, Ruby 3.3.6; no API needed)
mkdir app && cd app && mkdir gemfiles
printf 'source "https://rubygems.org"\ngem "colorize", "0.8.1"\n' > Gemfile
cp Gemfile gemfiles/alt.gemfile
gem install colorize -v 0.8.1 # a system copy also exists
export BUNDLE_GEMFILE=gemfiles/alt.gemfile
# variant cfg: bundle config set --local path vendor/bundle (writes gemfiles/.bundle/config)
# variant env: export BUNDLE_PATH=vendor/bundle
bundle install # → gemfiles/vendor/bundle/ruby/3.3.0/gems/colorize-0.8.1
# hand-written agent manifest: one patch to package/lib/colorize.rb (appends a marker),
# .socket/manifest.json + .socket/blobs/<afterHash>
socket-patch apply --offline # exit 0, "applied"
socket-patch vex --product pkg:gem/app@1.0.0 --output vex.json # exit 0, not_affected
bundle exec ruby -e 'require "colorize"; f=$LOADED_FEATURES.grep(/colorize.rb$/).first; puts f, File.read(f).include?("SOCKET_PATCHED_MARKER")'
# → ./gemfiles/vendor/bundle/ruby/3.3.0/gems/colorize-0.8.1/lib/colorize.rb false
grep -c SOCKET_PATCHED_MARKER "$(gem env gemdir)/gems/colorize-0.8.1/lib/colorize.rb" # 1 (unused copy)
Expected vs actual
- Expected: agent install-root discovery follows
Bundler::Settings (CLI_CONTRACT.md: the app config is $BUNDLE_APP_CONFIG/config, else .bundle/config, and the settings are resolved in Bundler's priority; ruby_crawler.rs:1391: a relative path resolves against Bundler.root). With an ambient BUNDLE_GEMFILE in a subdirectory, that means gemfiles/.bundle/config and gemfiles/vendor/bundle. Failing that, the command should refuse or warn the way hosted and vendored do, not attest.
- Actual:
apply patches the gem env copy and vex exits 0 with not_affected for a copy Bundler never loads.
OS × version
| OS |
Ruby |
Bundler |
Path from gemfiles/.bundle/config |
Path from env BUNDLE_PATH=vendor/bundle |
| Linux |
3.3.6 |
4.0.22 |
fail (×2) |
fail |
| Linux |
3.3.6 |
2.6.9 |
fail |
fail |
| macOS / Windows |
— |
— |
untested (path resolution is OS-independent) |
— |
Control: the same project without BUNDLE_GEMFILE (root .bundle/config with path vendor/bundle) patches the loaded copy (the e2e suites cover it). Tested on main 9c43dfc. I didn't bisect: no released version models Bundler.root for this.
Suspect code
crates/socket-patch-core/src/crawlers/ruby_crawler.rs:354 (discover_bundle_stores_impl): cwd is used as Bundler.root for the app config, the env BUNDLE_PATH and the default root.
ruby_crawler.rs:1320 (bundler_app_config_dir) and :1398 (resolve_bundle_path): both join onto the project root, not onto the directory of the effective BUNDLE_GEMFILE.
vex's installed-copy lookup goes through the same discovery.
No probe runs: this is OS-independent path resolution.
[agent] Found by the scheduled Bundler (RubyGems) bug-hunt routine (ledger #316).
Summary
Multi-Gemfile projects (the Appraisal /
gemfiles/*.gemfileCI-matrix layout) run Bundler withBUNDLE_GEMFILE=gemfiles/rails_7.gemfile. That movesBundler.roottogemfiles/, which has two effects:gemfiles/.bundle/config(Bundler.app_config_path=root.join(".bundle"));BUNDLE_PATHenv var, resolves againstgemfiles/.bundle installwithpath vendor/bundletherefore installs intogemfiles/vendor/bundle/ruby/<abi>.The agent-mode crawler always uses the
--cwdroot instead. It reads<cwd>/.bundle/config, resolvesBUNDLE_PATHagainst<cwd>, and probes<cwd>/vendor/bundle. The code's own doc comment says a relative value resolves "against the directory of the Gemfile (Bundler.root), not the process cwd" (ruby_crawler.rs:1391). It finds no store, falls back to thegem envhomes, and patches the system copy.vexthen attestsnot_affectedwhilebundle execloads the unpatchedgemfiles/vendor/bundlecopy. Nothing warns.Hosted and vendored modes already treat a
BUNDLE_GEMFILEoutside the root pair as unsupported (redirect_gem_bundle_gemfile_unsupported/gemfile_not_loaded). Agent mode andvexdon't consult it at all.Impact
Silent: the patch lands in a copy the app doesn't load, and the VEX document claims the vulnerability is mitigated. This shape is the standard GitHub Actions matrix,
env: BUNDLE_GEMFILE: gemfiles/<x>.gemfile. A relativeBUNDLE_PATH: vendor/bundleexport, orbundle config set --local path vendor/bundlerun under that env, both land ingemfiles/vendor/bundle.Repro (Linux, Ruby 3.3.6; no API needed)
Expected vs actual
Bundler::Settings(CLI_CONTRACT.md: the app config is$BUNDLE_APP_CONFIG/config, else.bundle/config, and the settings are resolved in Bundler's priority;ruby_crawler.rs:1391: a relative path resolves againstBundler.root). With an ambientBUNDLE_GEMFILEin a subdirectory, that meansgemfiles/.bundle/configandgemfiles/vendor/bundle. Failing that, the command should refuse or warn the way hosted and vendored do, not attest.applypatches thegem envcopy andvexexits 0 withnot_affectedfor a copy Bundler never loads.OS × version
gemfiles/.bundle/configBUNDLE_PATH=vendor/bundleControl: the same project without
BUNDLE_GEMFILE(root.bundle/configwithpath vendor/bundle) patches the loaded copy (the e2e suites cover it). Tested on main9c43dfc. I didn't bisect: no released version modelsBundler.rootfor this.Suspect code
crates/socket-patch-core/src/crawlers/ruby_crawler.rs:354(discover_bundle_stores_impl):cwdis used asBundler.rootfor the app config, the envBUNDLE_PATHand the default root.ruby_crawler.rs:1320(bundler_app_config_dir) and:1398(resolve_bundle_path): both join onto the project root, not onto the directory of the effectiveBUNDLE_GEMFILE.vex's installed-copy lookup goes through the same discovery.No probe runs: this is OS-independent path resolution.