[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).
[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 bothgems.rb/gems.lockedandGemfile/Gemfile.lock, bundler readsgems.rband ignores the other pair ("Multiple gemfiles (gems.rb and Gemfile) detected … bundler is ignoring them in favor of gems.rb and gems.locked"). Vendoring:path: ".socket/vendor/gem/<uuid>/…"wiring and the PATH section into the ignoredGemfile/Gemfile.lock,gems.rb/gems.lockeduntouched,status: success,applied: 1, exit 0.The next
bundle install(frozen or not) installs the upstream, unpatched gem.socket-patch vexon that checkout still emitsnot_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 withredirect_gem_gemfile_spellings_divergewhen 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 stubpatches/view/<uuid>that returnsblobContentforlib/vuln_gem.rb.Expected vs actual
Gemfilespelling only — agems.rbproject cannot vendor yet". A project bundler resolves throughgems.rbis a gems.rb project, so vendored mode should refuse it before any write (for a Gemfile-less project the backend's refusal isgemfile_missing). Alternatively it could wiregems.rb/gems.locked, mirroring the hosted rewriter's spelling choice. Andvexmust not attest a vendored patch the consuming manifest doesn't reference: bundler'sgems.lockedhas no PATH wiring.vexattestsnot_affected.Matrix (Linux; OS-independent file selection)
not_affected--frozengems.rbonly (control)getexits 1 withpackage_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 nogems.rbpresence check anywhere invendor/gem.rs.crates/socket-patch-core/src/patch/redirect/mod.rsrewrite_gem(modern = files.contains_key("gems.rb"), divergence guard).