Repository navigation
Fix gem apply/vex skipping platform gem copies (#1092) - #1395
Merged
Mikola Lysenko (mikolalysenko) merged 4 commits intoOct 11, 2026
Merged
Conversation
Assisted-by: Claude Code:claude-opus-5-5
A gem home can hold both builds of one gem version, such as ffi-1.17.2/ and ffi-1.17.2-x86_64-linux-gnu/. RubyGems and Bundler load the platform build, but agent-mode apply, rollback, apply --check and vex only ever looked at the plain dir. So apply patched a copy nothing loads, and vex attested not_affected while the loaded copy stayed vulnerable. Global mode (-g) had the same gap. The gem crawler now returns every verifying build of a version in a gem home (plain dir first, then each platform dir in name order), and the dispatch maps feed all of them to the commands that already act on every copy. A platform dir must parse back to the same gem name and version, so foo-1.0-2.0/ (the gem foo-1.0 at 2.0) is never taken as a build of foo 1.0. When builds sit side by side, a build whose bytes match none of the patch's variants is a different distribution rather than a locally edited copy. Apply leaves it untouched and fails, instead of overwriting it with another build's bytes. Fixes #1092 Assisted-by: Claude Code:claude-opus-5-5
Now that every build of a gem version in one gem home is a copy, a manifest with a separate record per build (?platform=ruby for x-1.0/, ?platform=x86_64-linux for x-1.0-x86_64-linux/) must be judged build by build: - rollback picked a variant from the first copy and rolled it back on every copy, failing on the other build and dropping its record. It now picks variants per copy, and skips a build that holds none of them when a sibling build does (nothing was ever written there). - vex judged each variant against every build, so two correctly patched builds were never attested. Each qualified variant now gets only the builds holding its distribution; a build holding none stays on every key, so the statement is still withheld. Also from review: the build-sibling apply gate is gem-only (PyPI site-packages dirs can share a parent), the gem home is listed once per lookup instead of once per patch, a dotted platform such as universal-java-1.8 is still recognised, and the contract no longer lists vex as a single-copy consumer. Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
Mikola Lysenko (mikolalysenko)
marked this pull request as ready for review
October 11, 2026 19:20
Collaborator
Author
|
BugBot review Generated by Claude Code |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 2688988. Configure here.
Collaborator
Author
|
[agent] Ready for review at
Generated by Claude Code |
Tanmay Singla (Tanmay182003)
approved these changes
Oct 11, 2026
Collaborator
Author
|
Ready for review at
Generated by Claude Code |
Mikola Lysenko (mikolalysenko)
deleted the
agent/fix-gem-every-platform-copy
branch
October 11, 2026 20:56
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
LLM Description written by Claude Code:claude-opus-5-5
Fixes #1092
Summary
A gem home can hold two builds of the same gem version, for example
ffi-1.17.2/(ruby platform) andffi-1.17.2-x86_64-linux-gnu/. RubyGems and Bundler load the platform build. Before this PR, agent-modeapply,rollback,apply --checkandvexonly looked at the plain dir. Soapplypatched a copy nothing loads,apply --checkreported "in sync", andvexattestednot_affectedwhile the loaded copy stayed vulnerable. Global mode (-g) had the same gap.After this PR, every build of the version is treated as an installed copy:
applypatches each build androllbackrestores each one.apply --checkandvexrefuse while any build is unpatched.Root cause
locate_gem_dir/locate_gem_dir_syncincrates/socket-patch-core/src/crawlers/ruby_crawler.rsassumed one build per gem home. They returned the plain<name>-<version>/dir and never looked at a-<platform>sibling. The commands downstream already act on every copy they are given (the multi-store gem fix), so the crawler was the gap.Changes
ruby_crawler.rs): newfind_all_by_purlsreturns every verifying build in a gem home. The plain dir comes first, then platform dirs in name order. The home is listed once per call.find_by_purlsandfind_each_by_purlkeep their single-copy contract (the first copy). A platform dir counts only when the text after<name>-<version>-starts with a letter, sofoo-1.0-2.0/(the gemfoo-1.0at 2.0) is not taken as a build offoo1.0. Dotted platforms such asuniversal-java-1.8still count.ecosystem_dispatch.rs): the gem arm and the vex verification-only stores usefind_all_by_purls, and every copy reaches the apply, rollback, vex and--checkmaps.apply.rs): an unqualified gem record normally overwrites a locally modified file with a warning. That policy assumes the copy is the only candidate. It no longer applies to a build that has a sibling build in the same gem home: a mismatch there means another distribution, so the build is left untouched and the run fails (no matching variant found), the same as any primary copy no variant matches. This gate is gem-only.rollback.rs): variants are chosen per copy instead of from the first copy only. A build that holds no variant, beside a sibling build that does, is skipped, because nothing was written there.vex.rs,vex_copy_sets): each qualified gem variant (?platform=) is judged only against the builds that hold its distribution. A build holding no variant stays on every key, so the statement is withheld.vexas a single-copy consumer.Test evidence
Each regression test below was run against
main's source (fix stashed) and failed, then passed with the fix:find_all_by_purls_returns_every_platform_copyis_platform_dir_of_rejects_another_gem_sharing_the_prefixapply_and_rollback_cover_every_platform_variant_copyvex_and_check_refuse_an_unpatched_platform_variant_copynot_affected;apply --checksaid "Patches are in sync"global_apply_covers_every_platform_variant_copy(-g)apply_refuses_a_platform_copy_holding_another_distributioneach_platform_build_uses_its_own_variantrollback_skips_a_platform_copy_holding_another_distributionhash_mismatchon the untouched buildPer-issue checklist:
applypatches only the ruby-platformx-1.0/dir when ax-1.0-x86_64-linux-gnu/sibling is also installed, so Bundler loads the unpatched platform gem whilevexattestsnot_affected#1092: apply patches every build (apply_and_rollback_cover_every_platform_variant_copy); vex andapply --checkrefuse while one build is unpatched (vex_and_check_refuse_an_unpatched_platform_variant_copy); global mode (global_apply_covers_every_platform_variant_copy);?platform=keys resolve to the right build (each_platform_build_uses_its_own_variant).Local runs:
cargo fmt --all -- --check: cleancargo clippy --workspace --all-features -- -D warnings: cleancargo test --workspace --all-features: 13901 passed, 13 failed. The 13 are unrelated to this change and specific to the sandbox: write-failure tests that rely onchmod(the sandbox runs as root, which ignores it) and one bun test that needs the live patch API. No gem test among them.cargo test -p socket-patch-cli --all-features --test e2e_redirect_gem_build --test e2e_vendor_gem_build -- --ignored(Ruby 3.3.6): 29 + 8 passed;e2e_redirect_gem_stale_install(non-ignored) passed in the workspace runcargo test -p socket-patch-cli --test apply gem: 30 passedReview notes
/code-review highran on the diff. Fixed: per-copy rollback narrowing, per-variant vex copy sets, the PyPI false positive of the sibling gate, one listing per gem home, dotted platforms, the contract wording, and the duplicated merge helper. Not changed:apply. Example: an-arm64-darwinbuild in avendor/bundleshared with a Linux host, when the manifest only has the Linux variant. This keeps the existing rule that a primary copy no variant matches fails loudly, because socket-patch can't tell which builds the running Ruby can load. Choosing byGem::Platform.localwould be a separate change.scan/hosted.rs,find_each_by_purl) still judges one dir per gem home. It is an advisory warning in hosted mode, not the agent apply/vex path Gem agentapplypatches only the ruby-platformx-1.0/dir when ax-1.0-x86_64-linux-gnu/sibling is also installed, so Bundler loads the unpatched platform gem whilevexattestsnot_affected#1092 covers. Follow-up.🤖 Generated with Claude Code
https://claude.ai/code/session_019iyRzgAFy9WDK4P1TZMAfJ
Generated by Claude Code