Skip to content

Hosted gem redirect wires gems.rb in a Gemfile/gems.rb twin locked by Bundler 1.17, which loads Gemfile, so the install stays unpatched while the in-run VEX attests it #751

Description

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

Summary

Bundler 1.x reads Gemfile before gems.rb. gems.rb only wins from Bundler 2.0 on (the prefer_gems_rb flag). When a project holds both spellings, hosted mode always follows the ≥ 2 order. On a project whose locks say BUNDLED WITH 1.17.3, scan --mode hosted:

  • rewrites only gems.rb/gems.locked;
  • reports status: success with only the usual redirect_gem_no_checksums_section / redirect_gem_frozen_install warnings;
  • writes an in-run --vex statement (not_affected).

Bundler 1.17.3 then installs from Gemfile/Gemfile.lock and gets the upstream (unpatched) gem. The post-install vex correctly refuses (exit 2), but the scan's own VEX document has already attested the CVE as mitigated.

crates/socket-patch-core/src/formats/gem/manifest.rs:18-20 notes that "1.x reads a Gemfile first, so callers treat a twin as ambiguous or follow the >= 2 order, as the hosted rewriter does". That trade-off isn't in docs/ecosystems.md or CLI_CONTRACT.md, and hosted mode neither warns nor refuses, even though both locks name the Bundler major (BUNDLED WITH 1.17.3). Vendored mode already refuses a twin outright (gemfile_not_loaded, after #341).

Impact

This produces a false OpenVEX not_affected and an unpatched runtime, with no warning, for Bundler 1.x projects that carry both spellings. It's a narrow shape, but it's exactly the "attestation for a patch that isn't applied" class.

Repro (hosted; patch API and registry mocked on loopback, real rubygems.org upstream)

gem install bundler -v 1.17.3            # Ruby <= 3.1 (1.17 calls String#untaint)
D='source "https://rubygems.org"\n\ngem "rake"\ngem "colorize", "~> 0.8.1"\n'
printf "$D" > Gemfile; printf "$D" > gems.rb
bundle _1.17.3_ config --local path vendor/bundle
BUNDLE_GEMFILE=Gemfile bundle _1.17.3_ lock; BUNDLE_GEMFILE=gems.rb bundle _1.17.3_ lock
socket-patch scan --mode hosted --json --yes --cwd . --vex out.vex.json --vex-product pkg:generic/app@1 ...
#   status=success, rewrittenFiles=["gems.rb"], vex.statements=1
bundle _1.17.3_ install                   # loads Gemfile (Bundler.default_gemfile => Gemfile)
grep -c SOCKET_PATCHED vendor/bundle/ruby/*/gems/colorize-0.8.1/lib/colorize.rb   # 0
socket-patch vex --output post.json ...   # exit 2 (correctly refuses after install)

Expected vs actual

  • Expected: docs/ecosystems.md says hosted mode "edits gems.rb + gems.locked when present (bundler prefers them over Gemfile)", and the vendored column qualifies that with "which bundler ≥ 2 loads instead". When the locks show Bundler 1.x, the redirect should wire the Gemfile pair Bundler 1 actually loads, or refuse/warn the way a divergent or unsupported manifest does (redirect_gem_gemfile_spellings_diverge, redirect_gem_bundle_gemfile_unsupported). It shouldn't attest in the same run.
  • Actual: it wires gems.rb silently, the in-run VEX attests, and Bundler 1.17 installs the unpatched gem.

Matrix (probe run https://github.com/SocketDev/socket-patch/actions/runs/37176834794)

OS Ruby Bundler Shape Bundler loads Scan In-run VEX Installed patched Post-install vex
ubuntu-latest 3.1 1.17.3 twin Gemfile success, wires gems.rb 1 statement no exit 2
windows-latest 2.7 1.17.3 twin Gemfile success, wires gems.rb 1 statement no exit 2
ubuntu-latest / windows-latest 2.7 / 3.1 1.17.3 Gemfile only / gems.rb only the same file success 1 yes 0
ubuntu-latest / windows-latest 2.7 / 3.1 2.2.33 twin gems.rb success 1 yes 0
ubuntu-latest / windows-latest 2.7 / 3.1 2.2.33 Gemfile only / gems.rb only the same file success 1 yes 0

(The ubuntu-latest Ruby 2.7 job ran the 1.17.3 cells too. Bundler 1.17 can't run on Ruby ≥ 3.2.)

Suspect code

  • crates/socket-patch-core/src/formats/gem/manifest.rs:76 (LoadedManifest::pair, the Default if gems_rb_present => "gems.rb" arm) and the comment at lines 18-20.
  • crates/socket-patch-core/src/hosted/engine.rs (keep_bundler_loaded_gem_files), which applies that order without looking at BUNDLED WITH.

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