Skip to content

Gem hosted redirect and setup ignore BUNDLE_GEMFILE from .bundle/config, so they wire Gemfile while bundler loads the configured manifest unpatched (VEX and setup --check still pass) #390

Description

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

Summary

Bundler takes its manifest from BUNDLE_GEMFILE: from the environment, or from the project's .bundle/config (bundle config set --local gemfile Gemfile.next, stored as BUNDLE_GEMFILE: "Gemfile.next"). That's the standard dual-boot / next-Rails layout (Gemfile + Gemfile.next + Gemfile.next.lock).

socket-patch never consults that setting (there's no BUNDLE_GEMFILE reference anywhere in crates/). It always picks gems.rb or Gemfile by filename (crates/socket-patch-core/src/patch/redirect/mod.rs:6051-6070, crates/socket-patch-core/src/setup/gem/mod.rs:101-116). It does already read the same .bundle/config for BUNDLE_PATH (crates/socket-patch-core/src/crawlers/ruby_crawler.rs:30-48).

The result:

  • Hosted (scan --mode hosted / get --mode hosted) rewrites Gemfile + Gemfile.lock, reports status: success, redirected: 1 with no warnings, and the in-run VEX attests not_affected (redirected). bundle install then resolves from Gemfile.next, still pointed at upstream, and the app loads the unpatched gem.
  • setup appends the plugin block to Gemfile. setup --check exits 0 ("configured"), but bundler never reads that file, so the plugin never registers. After bundle pristine + bundle install the gem stays unpatched with no warning.

(The post-install socket-patch vex does notice: not_applied, no document. So the false attestation is the in-run one, the same shape as #341.)

Repro (Bundler 4.0.17, Ruby 3.3.6, Linux)

Hermetic fixture: a mock upstream compact index + patch registry + patches API (a kept-alive copy of crates/socket-patch-cli/tests/e2e_redirect_gem_build.rs), serving vuln-gem 1.0.0 with a free patch.

mkdir proj && cd proj
printf 'source "%s/upstream"\n\ngem "vuln-gem"\n' "$MOCK" > Gemfile
cp Gemfile Gemfile.next
bundle config set --local path vendor/bundle
bundle install && BUNDLE_GEMFILE=Gemfile.next bundle install   # Gemfile.lock + Gemfile.next.lock
bundle config set --local gemfile Gemfile.next                   # .bundle/config: BUNDLE_GEMFILE: "Gemfile.next"
rm -rf vendor                                                    # no stale install

socket-patch scan --mode hosted --json --yes --vex vex.json --vex-product pkg:gem/[email protected] \
  --api-url "$MOCK" --org test-org --api-token x
#  -> status success, redirected 1, rewrittenFiles [Gemfile, Gemfile.lock], warnings []
#  -> vex.json: not_affected / inline_mitigations_already_exist "Patched via Socket patch … (redirected)"

bundle install
bundle exec ruby -e 'require "vuln_gem"; puts VulnGem.status; puts Bundler.default_gemfile'
#  -> VULNERABLE
#  -> /…/proj/Gemfile.next

setup variant, on the same layout:

socket-patch setup --yes            # + Gemfile, + .socket/bundler-plugin
socket-patch get "$UUID" --yes …    # agent mode, applies once
socket-patch setup --check; echo $? # 0
bundle install                      # no "Installed plugin socket-patch" line: plugin never registers
bundle pristine vuln-gem && bundle install
bundle exec ruby -e 'require "vuln_gem"; puts VulnGem.status'   # VULNERABLE

Expected vs actual

  • Expected: the rewriter edits the manifest/lock pair bundler actually loads, which is the principle the code already states for gems.rb vs Gemfile ("the gem rewriter picks the pair bundler reads", crates/socket-patch-cli/src/commands/scan/hosted.rs:83-85; docs/ecosystems.md: "edits gems.rb + gems.locked when present (bundler prefers them over Gemfile)"). Otherwise it should fail closed with a warning, like redirect_gem_gemfile_spellings_diverge, and never emit a not_affected attestation or a passing setup --check for a file bundler ignores.
  • Actual: it silently wires Gemfile, reports success, attests not_affected, and setup --check passes, while bundler installs and loads upstream bytes.

Matrix

OS Bundler Hosted scan + in-run VEX setup + --check
Linux 4.0.17 fail (reproduced twice, two fresh projects) fail
Linux 2.x untested; BUNDLE_GEMFILE / config gemfile behave the same since Bundler 1.x untested
macOS / Windows any untested; the selection logic is OS-independent untested

The env-var-only form (BUNDLE_GEMFILE=Gemfile.next bundle install in a CI job) has the same outcome. It's arguably harder for socket-patch to see, but the committed .bundle/config form is on disk in the project, where the crawler already reads BUNDLE_PATH.

Suspect code

  • crates/socket-patch-core/src/patch/redirect/mod.rs:6051-6070: the (gemfile_name, lock_name) choice considers only gems.rb / Gemfile.
  • crates/socket-patch-cli/src/commands/scan/hosted.rs:81-88: the fixed list of gem manifest names handed to the rewriter.
  • crates/socket-patch-core/src/setup/gem/mod.rs:101-116: discover_bundler_project ignores BUNDLE_GEMFILE. plugins.rb.tmpl also hard-codes Gemfile.lock in digest_inputs.
  • The vendored mode (crates/socket-patch-core/src/vendor/gem.rs) most likely shares the gap. I didn't verify it this run.

Tested on main f6b7fb9 (v4.0.0).

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