GeezerVison is showing github.com — Vendored gem mode wires Gemfile when gems.rb is also present, so bundler installs the unpatched gem while vex attests it · Issue #341 · SocketDev/socket-patch
Vendored gem mode wires Gemfile when gems.rb is also present, so bundler installs the unpatched gem while vex attests it #341Description[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/[email protected] …
Warning: pkg:gem/[email protected]: 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).
Activitymikolalysenko commented on Sep 30, 2026 CollaboratorAuthor More actions [agent] Triaged: priority:p1 (RubyGems/Bundler). Not a duplicate, and no open or merged PR fixes it. Confirmed on main f6b7fb9: vendor/gem.rs:86-87 hardcodes GEMFILE = "Gemfile" / GEMFILE_LOCK = "Gemfile.lock", and nothing in vendor/gem.rs checks for gems.rb. The hosted rewriter's spelling choice (rewrite_gem, modern = files.contains_key("gems.rb")) isn't shared with the vendored backend. This is a different cause from #340 (hosted single-line declaration regex), so the two aren't clustered.
Generated by Claude Code mikolalysenko commented on Oct 1, 2026 CollaboratorAuthor More actions [agent] Re-triaged on main 2463257 (after the v5 consolidation in #277, which moved vendoring to service artifacts). This still reproduces with Bundler 4.0.17 on Ruby 3.3.6 (Linux).
I ran the e2e_vendor_gem_build direct-dep capstone (rack ~> 3.1, prebuilt service-artifact fixture) with a gems.rb / gems.locked twin staged next to Gemfile / Gemfile.lock before vendor, and the twin included in the fresh checkout:
vendor exits 0 and reports applied: 1. It rewrites only Gemfile / Gemfile.lock; gems.rb is left as gem "rack", "~> 3.1".
- The frozen fresh-checkout
bundle install succeeds, but Bundler reads gems.rb, so bundle exec ruby -e 'require "rack"' loads upstream rack. The capstone's runtime probe fails with probe constant missing after require.
crates/socket-patch-core/src/vendor/gem.rs:86-87 still hardcodes Gemfile / Gemfile.lock, and nothing in that file checks for gems.rb.
Generated by Claude Code mikolalysenko commented on Oct 1, 2026 CollaboratorAuthor More actions [agent] Claiming this issue (with #390). Shared root cause: there is no single "which manifest does Bundler load" resolver. Hosted and vendored modes pick the Gemfile by filename, or hardcode Gemfile, and ignore gems.rb precedence and BUNDLE_GEMFILE. Branch: agent/fix-bundler-manifest-resolution. Claim-ID: 2026-10-01T06:22:10Z-b5b0c8
Generated by Claude Code mikolalysenko commented on Oct 1, 2026 CollaboratorAuthor More actions
MetadataMetadataDevelopmentNo branches or pull requestsIssue actions
|