Repository navigation
Gem agent apply patches only the ruby-platform x-1.0/ dir when a x-1.0-x86_64-linux-gnu/ sibling is also installed, so Bundler loads the unpatched platform gem while vex attests not_affected #1092
Description
[agent] Found by the scheduled Bundler (RubyGems) bug-hunt routine (ledger #316).
Summary
A gem home can hold both the ruby-platform build and a native-platform build of the same gem version, for example ffi-1.17.2/ and ffi-1.17.2-x86_64-linux-gnu/. This happens after any gem install <x> --platform ruby, or after a project toggles force_ruby_platform. Bundler 2.6+ locks list every platform variant (ffi (1.17.2), ffi (1.17.2-x86_64-linux-gnu), …), and on x86_64 Linux Bundler loads the -x86_64-linux-gnu copy.
In agent mode the gem crawler resolves pkg:gem/ffi@1.17.2 to exactly one directory, and the plain ffi-1.17.2/ dir always wins (locate_gem_dir). So apply patches only the ruby-platform copy and leaves the loaded platform copy unpatched. vex checks the same single directory and writes not_affected / inline_mitigations_already_exist. A manifest key qualified as pkg:gem/ffi@1.17.2?platform=x86_64-linux-gnu goes to the same wrong dir, because parse_gem_purl drops the qualifier.
Impact
The vulnerable code keeps running and a false VEX attestation is produced. Nothing warns. apply reports 1 of 1 targeted patch applied, and a re-run says already patched.
Repro (shared gem home, Bundler 4.0.18 / Ruby 3.3.6 and Bundler 2.6.9 / Ruby 3.1.6)
export GEM_HOME=$PWD/gh GEM_PATH=$PWD/gh
mkdir empty && (cd empty && gem install ffi -v 1.17.2 --platform ruby --no-document && gem install ffi -v 1.17.2 --no-document)
ls gh/gems # ffi-1.17.2 ffi-1.17.2-x86_64-linux-gnu
mkdir app && cd app
printf 'source "https://rubygems.org"\ngem "ffi", "1.17.2"\n' > Gemfile
bundle install # lock lists ffi (1.17.2) + ffi (1.17.2-<platform>) …
# hand-written agent manifest: pkg:gem/ffi@1.17.2, file package/lib/ffi/version.rb
# (identical bytes in both builds), afterHash = original + "# SOCKET-PATCH-BUNDLER-MARKER", blob in .socket/blobs
socket-patch apply --offline --ecosystems gem
# Summary: 1 of 1 targeted patch applied
grep -c MARKER $GEM_HOME/gems/ffi-1.17.2*/lib/ffi/version.rb
# ffi-1.17.2-x86_64-linux-gnu/lib/ffi/version.rb:0
# ffi-1.17.2/lib/ffi/version.rb:1
socket-patch vex --offline --product pkg:gem/app@1.0.0 -O vex.json
# VEX: not_affected
bundle exec ruby -e 'require "ffi"; s=Gem.loaded_specs["ffi"]; puts s.full_name, File.read(s.full_gem_path+"/lib/ffi/version.rb").include?("MARKER")'
# ffi-1.17.2-x86_64-linux-gnu
# falseA project-local shape behaves the same way: path vendor/bundle, first installed with force_ruby_platform true, then bundle lock --add-platform x86_64-linux-gnu and a platform install, so vendor/bundle/ruby/3.3.0/gems/ holds both dirs.
Expected vs actual
- Expected (CLI_CONTRACT.md, agent rows, around lines 393 and 564): "
apply/rollbackpatch/restore every copy", and forvex, "Every installed copy the crawler finds for the purl … must hash to the patched bytes … One unpatched copy omits the purl (not_applied/hash_mismatch)". Both platform dirs are installed copies offfi@1.17.2, soapplyshould patch both (or at least the one Bundler loads), andvexshould refuse while either is unpatched. - Actual: only
ffi-1.17.2/is patched, andvexattestsnot_affectedwhileffi-1.17.2-x86_64-linux-gnu/loads unpatched.
Matrix
| OS | Ruby | Bundler | Shape | Result |
|---|---|---|---|---|
| Linux | 3.3.6 | 4.0.18 | vendor/bundle, force_ruby_platform toggled |
fails (unqualified and ?platform=x86_64-linux-gnu keys) |
| Linux | 3.3.6 | 4.0.18 | shared GEM_HOME, both builds from gem install |
fails |
| Linux | 3.1.6 | 2.6.9 | shared GEM_HOME, both builds from gem install |
fails |
| macOS / Windows | — | — | — | untested. The logic is OS-independent, and Windows x64-mingw-ucrt builds use the same suffix scheme |
Main 05ecc6e. Published v4.0.0 also patches only ffi-1.17.2/ (its vex stops earlier with ecosystem_not_setup), so this predates v5 and isn't a recent regression.
Suspect code
crates/socket-patch-core/src/crawlers/ruby_crawler.rs:978-983(locate_gem_dir): "Only one platform is installed per environment, so we return the exact dir when present". That assumption is false for shared gem homes and for toggledforce_ruby_platforminstalls.crates/socket-patch-core/src/crawlers/ruby_crawler.rs:1050-1057(locate_gem_dir_sync, used byfind_each_by_purl): same exact-dir-first rule.crates/socket-patch-core/src/utils/purl.rs:279(parse_gem_purl): the?platform=qualifier is discarded, so a platform-qualified manifest key can't pick its dir either.- The unit test
locate_gem_dir_prefers_exact_over_platform(ruby_crawler.rs:4173) pins the current behaviour.
No probe runs: found and confirmed on Linux only.
Activity
mikolalysenko commentedon Oct 7, 2026
[agent] Global mode has the same problem, without Bundler involved. On the same shared
GEM_HOME(bothffi-1.17.2/andffi-1.17.2-x86_64-linux-gnu/installed),socket-patch apply -g --global-prefix $GEM_HOME/gems --offlinereports1 of 1 targeted patch appliedbut only patchesffi-1.17.2/lib/ffi/version.rb. Plain RubyGems (ruby -e 'require "ffi"') activatesffi-1.17.2-x86_64-linux-gnu, which stays unpatched. Main05ecc6e, Ruby 3.3.6, Linux.Generated by Claude Code
mikolalysenko commentedon Oct 7, 2026
[agent] Triage: priority:p1 (RubyGems/Bundler). Confirmed in code on main:
locate_gem_dir(crates/socket-patch-core/src/crawlers/ruby_crawler.rs:978-981) andlocate_gem_dir_sync(line 1050) still assume one platform per gem home and return the plain<name>-<version>/dir first, and thelocate_gem_dir_prefers_exact_over_platformtest pins that. No open or merged PR covers it, and it isn't a duplicate. #952 is also a gem agent-crawl wrong-copy bug, but its cause is different (BUNDLE_GEMFILEmovingBundler.root), so the two aren't clustered.Generated by Claude Code
mikolalysenko commentedon Oct 8, 2026
[agent] Re-triage on main
b96a785(Bundler 4.0.18 / Ruby 3.3.6, sharedGEM_HOMEwithffi-1.17.2/+ffi-1.17.2-x86_64-linux-gnu/): this still reproduces. The newapply --checkgate from #1029 inherits it:The same happens with the key
pkg:gem/ffi@1.17.2?platform=x86_64-linux-gnu. CLI_CONTRACT says that a release variant "is judged only on the copies holding its distribution", but--checkjudges the ruby-platform dir and reports in sync, while the loaded-x86_64-linux-gnucopy is unpatched. So a CI gate built onapply --checkpasses on this shape too.rollbackrestores the ruby dir correctly (the platform dir was never touched).Generated by Claude Code
bundler: run 32 ledger (#1125, #1092/#952 comments)
mikolalysenko commentedon Oct 9, 2026
v5 triage: P2, not a release blocker. Retain Ruby agent selection among coinstalled platform variants at P2. Default hosted/vendored lockfile behavior is the release focus.
This follows the maintainer's release scope: one normally completing CLI instance, prioritizing valid-lockfile patch/install behavior, compatibility, and actionable CLI UX.
mikolalysenko commentedon Oct 11, 2026
[agent] Claiming this issue (shared root cause: the gem crawler resolves a gem@version to one directory per gem home, so coinstalled platform variants like
ffi-1.17.2/+ffi-1.17.2-x86_64-linux-gnu/are never all patched or checked). Branch: agent/fix-gem-every-platform-copy. Claim-ID: 2026-10-11T18:21:19Z-758ce1Generated by Claude Code
mikolalysenko commentedon Oct 11, 2026
[agent] Draft PR: #1395
Generated by Claude Code
Patch and check every installed build of a gem