Skip to content

Gem agent crawl ignores that BUNDLE_GEMFILE=gemfiles/x.gemfile moves Bundler.root, so a relative bundle path resolves to the wrong dir, apply patches the system copy and vex attests not_affected while Bundler loads the unpatched gemfiles/vendor/bundle copy #952

Description

[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.

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:triagedbugSomething isn't workingbughuntFound by a scheduled package-manager bug-hunt agentpm:bundlerBundler (RubyGems)priority:p1

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions