Skip to content

Gem crawler misses a Bundler 4 bundle install --standalone tree (./bundle), so agent apply patches the system copy and VEX attests not_affected while the app loads the unpatched standalone copy #796

Description

[agent] Found by the scheduled Bundler (RubyGems) bug-hunt routine (ledger #316).

Summary

bundle install --standalone installs the bundle into ./bundle/ruby/<api>/gems/ and writes bundle/bundler/setup.rb, which hardcodes those load paths. Apps load it with require_relative "bundle/bundler/setup". This is the usual layout for Lambda functions and self-contained CLIs.

Bundler 2.x also records BUNDLE_PATH: "bundle" in .bundle/config, and the ruby crawler follows that. Bundler 4 no longer remembers CLI flags, so it writes no .bundle/config at all. The crawler's roots are only config path, $BUNDLE_PATH, vendor/bundle and the gem env homes, so on Bundler 4 it never sees ./bundle:

  • If no other copy exists, apply reports package_not_installed ("Resolved by the project lockfile but not installed on this host (lockfile-only)"), but the gem is installed.
  • If the same name-version is also in a gem env home (a common case: another project, or a global gem install), apply patches that copy and reports success. vex then attests not_affected ("Patched via Socket patch …"), while the app still loads the unpatched ./bundle copy.

Impact

False VEX attestation, and an unpatched runtime that reports applied. Every Bundler 4 standalone project is affected.

Repro (Linux, Ruby 3.3.6, Bundler 4.0.17, main 045d7ec; any marker patch works)

gem install rack -v 3.2.7            # an ambient copy of the same version
mkdir app && cd app
printf 'source "https://rubygems.org"\ngem "rack", "~> 3.1"\n' > Gemfile
bundle _4.0.17_ install --standalone  # -> ./bundle/ruby/3.3.0/gems/rack-3.2.7, no .bundle/config
# stage .socket/manifest.json + blob for pkg:gem/rack@3.2.7 (patch lib/rack.rb, append a probe constant)
socket-patch apply --json            # success, 1 applied
grep -c PROBE "$(gem env gemdir)/gems/rack-3.2.7/lib/rack.rb"   # 1  (system copy patched)
grep -c PROBE bundle/ruby/3.3.0/gems/rack-3.2.7/lib/rack.rb      # 0  (standalone copy untouched)
socket-patch vex --offline --product pkg:gem/app@1 -O vex.json  # not_affected / inline_mitigations_already_exist
ruby -e 'require_relative "bundle/bundler/setup"; require "rack"; p defined?(Rack::PROBE)'  # nil, loads ./bundle/.../rack.rb

If you skip the gem install line, apply skips with package_not_installed, and vex omits the purl (package_not_found).

Expected vs actual

  • Expected: the crawler covers the install roots Bundler actually uses. ruby_crawler.rs documents probing "the bundler install roots, probed in bundler's own precedence order", and CLI_CONTRACT.md's VEX rules say an unpatched installed copy must never be attested. The ./bundle tree is what bundle/bundler/setup.rb loads.
  • Actual: on Bundler 4 the standalone root is invisible. The ambient copy is patched and attested instead.

OS × version

OS Ruby Bundler .bundle/config after --standalone Standalone copy patched VEX
Linux 3.3.6 2.4.22 BUNDLE_PATH: "bundle" yes (pass) correct
Linux 3.3.6 2.5.22 BUNDLE_PATH: "bundle" yes (pass) correct
Linux 3.3.6 2.6.9 BUNDLE_PATH: "bundle" yes (pass) correct
Linux 3.3.6 2.7.2 BUNDLE_PATH: "bundle" yes (pass) correct
Linux 3.3.6 4.0.17 none no (×2 runs) false not_affected

The crawler logic is OS-independent. The v4.0.0 release behaves the same way: it patches only the system copy. The trigger is Bundler 4 dropping remembered flags, not a socket-patch commit.

Suspect code

  • crates/socket-patch-core/src/crawlers/ruby_crawler.rs:291-330 (discover_bundle_stores_with_env): the root list is config path → $BUNDLE_PATH → vendor/bundle, with no probe for a standalone bundle/ root (for example, one marked by bundle/bundler/setup.rb).
  • ruby_crawler.rs:77-125 (gem_paths_and_discovery): because no project store is found, the gem env homes are appended, and those are what get patched and attested.

Hosted mode's stale-install guard probably misses the same root, because a re-run of bundle install --standalone would reuse the unpatched ./bundle copy. I haven't verified that; it's in the ledger backlog.

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions