Skip to content

Vendored gem mode wires Gemfile when gems.rb is also present, so bundler installs the unpatched gem while vex attests it #341

Description

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

Summary

The vendored gem backend hardcodes Gemfile / Gemfile.lock (vendor/gem.rs:86, :244). When a project has both gems.rb/gems.locked and Gemfile/Gemfile.lock, bundler reads gems.rb and ignores the other pair ("Multiple gemfiles (gems.rb and Gemfile) detected … bundler is ignoring them in favor of gems.rb and gems.locked"). Vendoring:

  • writes the path: ".socket/vendor/gem/<uuid>/…" wiring and the PATH section into the ignored Gemfile/Gemfile.lock,
  • leaves gems.rb / gems.locked untouched,
  • reports status: success, applied: 1, exit 0.

The next bundle install (frozen or not) installs the upstream, unpatched gem. socket-patch vex on that checkout still emits not_affected … (vendored), with only a "live tree carries different bytes" warning.

Impact

The patch silently does nothing, and the VEX document attests a live CVE as mitigated. The hosted rewriter handles this exact layout: it follows bundler and edits gems.rb, and fails closed with redirect_gem_gemfile_spellings_diverge when the two spellings differ. Vendored mode has no such check. Twin spellings are a normal state mid-migration between the two names.

Repro

This uses the hermetic fixture from crates/socket-patch-cli/tests/e2e_redirect_gem_build.rs (wiremock upstream compact index), plus a stub patches/view/<uuid> that returns blobContent for lib/vuln_gem.rb.

# gems.rb + gems.locked from `bundle install` (path vendor/bundle), then create the twin:
$ cp gems.rb Gemfile && cp gems.locked Gemfile.lock
$ socket-patch get <uuid> --mode vendored --vendor-source build --json --yes --api-url $API --org test-org --api-token fake
  -> exit 0, status success, vendor.status success, applied 1
$ git diff --no-index gems.rb Gemfile
-gem "vuln-gem"
+gem "vuln-gem", "1.0.0", path: ".socket/vendor/gem/<uuid>/vuln-gem-1.0.0"
# gems.locked unchanged; Gemfile.lock gained the PATH section + `vuln-gem (= 1.0.0)!`

# fresh checkout (gems.rb, gems.locked, Gemfile, Gemfile.lock, .socket, .bundle):
$ bundle install            # also with --frozen / BUNDLE_FROZEN=true
Fetching vuln-gem 1.0.0
Installing vuln-gem 1.0.0
Multiple gemfiles (gems.rb and Gemfile) detected. … bundler is ignoring them in favor of gems.rb and gems.locked.
exit 0
$ bundle exec ruby -e 'require "vuln_gem"; p defined?(PATCHED_7c8d9e0f)'
nil                                   # upstream bytes loaded from vendor/bundle, not .socket/vendor
$ socket-patch vex --output v.json --product pkg:gem/app@1.0.0 …
Warning: pkg:gem/vuln-gem@1.0.0: the installed tree does not match its vendored artifact; …
Wrote OpenVEX document with 1 statement   -> not_affected GHSA-x "Patched via Socket patch <uuid> (vendored)"

Expected vs actual

  • Expected: docs/ecosystems.md (RubyGems row, Vendored column) says "Gemfile spelling only — a gems.rb project cannot vendor yet". A project bundler resolves through gems.rb is a gems.rb project, so vendored mode should refuse it before any write (for a Gemfile-less project the backend's refusal is gemfile_missing). Alternatively it could wire gems.rb / gems.locked, mirroring the hosted rewriter's spelling choice. And vex must not attest a vendored patch the consuming manifest doesn't reference: bundler's gems.locked has no PATH wiring.
  • Actual: the ignored spelling is wired, the command reports success, bundler installs upstream bytes, and vex attests not_affected.

Matrix (Linux; OS-independent file selection)

OS Ruby Bundler install result
Linux 3.3.6 4.0.9 unfrozen fail: upstream bytes installed; vex not_affected
Linux 3.3.6 2.4.22 --frozen fail: upstream bytes installed
Linux 3.3.6 4.0.9 gems.rb only (control) nothing written; get exits 1 with package_not_installed / vendor_fetch_unverifiable (cause not investigated yet)

Each fail reproduced twice. macOS/Windows weren't probed, because the file choice is a hardcoded constant.

First bad

Not bisected. Present on main f6b7fb9 (4.0.0).

Suspect code

  • crates/socket-patch-core/src/vendor/gem.rs:86-87 (const GEMFILE = "Gemfile", GEMFILE_LOCK = "Gemfile.lock") and :244 (the only project-file read). There's no gems.rb presence check anywhere in vendor/gem.rs.
  • Hosted counterpart for comparison: crates/socket-patch-core/src/patch/redirect/mod.rs rewrite_gem (modern = files.contains_key("gems.rb"), divergence guard).

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