[agent] Found by the scheduled Bundler (RubyGems) bug-hunt routine (ledger #316).
Summary
Bundler's mirror.all setting (bundle config set --local mirror.all <url>, BUNDLE_MIRROR__ALL: "<url>" in .bundle/config, or the BUNDLE_MIRROR__ALL env var) sends every source to one mirror. That includes the source "<patch registry>" do … end block the hosted rewrite writes (Bundler::Settings::Mirrors#for returns @all for any URI; see bundler/mirror.rb). Corporate setups often point it at an Artifactory or Nexus rubygems proxy. Such a mirror serves the upstream vuln-gem-1.0.0.gem for the patch-registry source, because it has the same name and version.
scan --mode hosted / get --mode hosted never read mirror settings. A project whose committed .bundle/config sets mirror.all gets the redirect written, redirected: 1, and an in-run VEX not_affected, with no warning. Then:
- Lock with no CHECKSUMS (every Bundler < 2.6 lock, and the 2.6 / 2.7 default): the next
bundle install exits 0 and installs the unpatched upstream bytes. The lock now records the patch registry as the gem's remote:, so the checkout looks patched. bundle exec loads the vulnerable code, and a frozen reinstall also exits 0.
- CHECKSUMS lock (Bundler 2.6+ with checksums, the 4.x default): every
bundle install fails with exit 37, "Bundler found mismatched checksums". Bundler's own printed remedy is "remove the matching checksum … run bundle install", which leads straight to the unpatched install above.
The post-install standalone vex hash-verifies the tree and correctly omits the gem (not_applied). The false attestation comes from the same-run --vex, and the scan surfaces nothing about the mirror.
Impact
Silent loss of the security patch on a CHECKSUMS-less lock, while the scan output, the committed Gemfile and lock, and the same-run VEX all say it's patched. On CHECKSUMS locks it breaks installs, and the obvious fix reinstalls the vulnerable gem. This is the same class as #483 (cache_path) and #507 / #390 (BUNDLE_GEMFILE): a setting in the project's own .bundle/config, which the hosted engine already reads, defeats the redirect.
Repro
The sandbox can't reach the patch API, so this uses the repo's hosted capstone crates/socket-patch-cli/tests/e2e_redirect_gem_build.rs (real gem build, wiremock compact index for /upstream/ and the patch registry, and real bundle install). The only change is a temporary hook in redirect_scanned_project, placed just before the scan --mode hosted --vex call:
// before the scan: commit a mirror for every source, pointing at the upstream index
let m = format!("{}/upstream/", server.uri());
bundle(&proj, &["config", "set", "--local", "mirror.all", &m]);
Then, in gem_hosted_fresh_checkout_bundle_install_installs_patched_bytes_and_vex_verifies, do a fresh checkout (Gemfile, lock, .socket/, .bundle/), then bundle install, then bundle exec ruby -e 'require "vuln_gem"; puts VulnGem.status':
BUNDLER_VERSION=2.5.22 SOCKET_PATCH_BUNDLER_E2E_VERSION=2.5.22 \
cargo test --release -p socket-patch-cli --test e2e_redirect_gem_build \
gem_hosted_fresh_checkout_bundle_install_installs_patched_bytes_and_vex_verifies -- --ignored --nocapture
Output (Bundler 2.5.22):
scan: exit 0, redirect.redirected=1, warnings: redirect_gem_no_checksums_section, redirect_gem_frozen_install (nothing about the mirror)
in-run out.vex.json: pkg:gem/vuln-gem@1.0.0 "status": "not_affected" (redirected)
bundle install: exit 0
Fetching gem metadata from http://127.0.0.1:44827/upstream/..
Fetching vuln-gem 1.0.0 / Installing vuln-gem 1.0.0
Gemfile.lock after install:
GEM
remote: http://127.0.0.1:44827/patch-registry/gem/<token>/<uuid>/
specs:
vuln-gem (1.0.0)
DEPENDENCIES
vuln-gem (= 1.0.0)!
installed lib/vuln_gem.rb == patched bytes: false
bundle exec … VulnGem.status => VULNERABLE
BUNDLE_FROZEN=true bundle install: exit 0
standalone vex: omits vuln-gem (not_applied), exit 1
CHECKSUMS arm (the same hook with the fixture's checksums_lock = true, Bundler 4.0.17 and 2.6.9):
bundle install: exit 37
Bundler found mismatched checksums. This is a potential security risk.
vuln-gem (1.0.0) sha256=5d10a0… from the lockfile CHECKSUMS at Gemfile.lock:22:20
vuln-gem (1.0.0) sha256=f8a737… from the API at http://127.0.0.1:41757/upstream/
… you can: 1. remove the matching checksum in Gemfile.lock:22:20 2. run `bundle install`
Real-world shape: bundle config set --local mirror.all https://artifactory.example/api/gems/rubygems/, commit .bundle/config, run socket-patch scan --mode hosted --vex …, then bundle install.
Expected vs actual
- Expected: docs/ecosystems.md says the hosted gem mode wires a per-dep
source block, so bundle install fetches the patched gem from the Socket patch registry. CLI_CONTRACT.md's "Gem stale-install guard" says the same-run --vex "must never attest a CVE its own warning says is live". When the project's bundler config routes the patch-registry source elsewhere (mirror.all, or a mirror.<patch-registry-url> key), the run should refuse or warn, for example redirect_gem_mirror_overrides_source with the remedy (scope the mirror to mirror.https://rubygems.org, or unset mirror.all). It should also exclude that gem from the same-run VEX, as it already does for redirect_gem_bundle_gemfile_unsupported and stale installs. A BUNDLE_MIRROR__ALL set only in CI can't be seen at scan time, so that limitation belongs in the docs.
- Actual: no warning,
redirected: 1, and in-run VEX not_affected. The next install is silently unpatched (no CHECKSUMS), or fails with exit 37 and a remedy that leads to the unpatched install (CHECKSUMS).
OS × version
| OS |
Ruby |
Bundler |
Lock |
Result |
| Linux |
3.3.6 |
2.4.22 |
no CHECKSUMS |
reproduces: unpatched install, exit 0 |
| Linux |
3.3.6 |
2.5.22 |
no CHECKSUMS |
reproduces (×2) |
| Linux |
3.3.6 |
4.0.17 |
no CHECKSUMS |
reproduces (×2) |
| Linux |
3.3.6 |
2.6.9 |
CHECKSUMS |
install fails, exit 37 (no scan warning; in-run VEX not_affected) |
| Linux |
3.3.6 |
4.0.17 |
CHECKSUMS |
install fails, exit 37 (same) |
| macOS / Windows |
any |
any |
any |
not run; the setting is read from the same .bundle/config and the env, so this is OS-independent |
First bad version: not bisected. Mirror settings have never been read anywhere in crates/ (grep -ri mirror finds no Bundler handling), so this has been present since the hosted gem mode shipped.
Suspect code
crates/socket-patch-core/src/hosted/engine.rs:532 (keep_bundler_loaded_gem_files) reads the project's bundler config only for BUNDLE_GEMFILE. That's the natural place to also detect BUNDLE_MIRROR__ALL (and a BUNDLE_MIRROR__<patch-registry host> key) from the app config and the env, honoring BUNDLE_IGNORE_CONFIG.
crates/socket-patch-core/src/crawlers/ruby_crawler.rs:1169 (bundle_config_setting) is the existing flat-YAML reader to reuse.
crates/socket-patch-core/src/patch/redirect/mod.rs:5405 (rewrite_gem) is where the source block is written.
No probe runs: the reproduction is Linux-only because the defect is in config reading, not in platform paths.
[agent] Found by the scheduled Bundler (RubyGems) bug-hunt routine (ledger #316).
Summary
Bundler's
mirror.allsetting (bundle config set --local mirror.all <url>,BUNDLE_MIRROR__ALL: "<url>"in.bundle/config, or theBUNDLE_MIRROR__ALLenv var) sends every source to one mirror. That includes thesource "<patch registry>" do … endblock the hosted rewrite writes (Bundler::Settings::Mirrors#forreturns@allfor any URI; seebundler/mirror.rb). Corporate setups often point it at an Artifactory or Nexus rubygems proxy. Such a mirror serves the upstreamvuln-gem-1.0.0.gemfor the patch-registry source, because it has the same name and version.scan --mode hosted/get --mode hostednever read mirror settings. A project whose committed.bundle/configsetsmirror.allgets the redirect written,redirected: 1, and an in-run VEXnot_affected, with no warning. Then:bundle installexits 0 and installs the unpatched upstream bytes. The lock now records the patch registry as the gem'sremote:, so the checkout looks patched.bundle execloads the vulnerable code, and a frozen reinstall also exits 0.bundle installfails with exit 37, "Bundler found mismatched checksums". Bundler's own printed remedy is "remove the matching checksum … runbundle install", which leads straight to the unpatched install above.The post-install standalone
vexhash-verifies the tree and correctly omits the gem (not_applied). The false attestation comes from the same-run--vex, and the scan surfaces nothing about the mirror.Impact
Silent loss of the security patch on a CHECKSUMS-less lock, while the scan output, the committed Gemfile and lock, and the same-run VEX all say it's patched. On CHECKSUMS locks it breaks installs, and the obvious fix reinstalls the vulnerable gem. This is the same class as #483 (
cache_path) and #507 / #390 (BUNDLE_GEMFILE): a setting in the project's own.bundle/config, which the hosted engine already reads, defeats the redirect.Repro
The sandbox can't reach the patch API, so this uses the repo's hosted capstone
crates/socket-patch-cli/tests/e2e_redirect_gem_build.rs(realgem build, wiremock compact index for/upstream/and the patch registry, and realbundle install). The only change is a temporary hook inredirect_scanned_project, placed just before thescan --mode hosted --vexcall:Then, in
gem_hosted_fresh_checkout_bundle_install_installs_patched_bytes_and_vex_verifies, do a fresh checkout (Gemfile, lock,.socket/,.bundle/), thenbundle install, thenbundle exec ruby -e 'require "vuln_gem"; puts VulnGem.status':Output (Bundler 2.5.22):
CHECKSUMS arm (the same hook with the fixture's
checksums_lock = true, Bundler 4.0.17 and 2.6.9):Real-world shape:
bundle config set --local mirror.all https://artifactory.example/api/gems/rubygems/, commit.bundle/config, runsocket-patch scan --mode hosted --vex …, thenbundle install.Expected vs actual
sourceblock, sobundle installfetches the patched gem from the Socket patch registry. CLI_CONTRACT.md's "Gem stale-install guard" says the same-run--vex"must never attest a CVE its own warning says is live". When the project's bundler config routes the patch-registry source elsewhere (mirror.all, or amirror.<patch-registry-url>key), the run should refuse or warn, for exampleredirect_gem_mirror_overrides_sourcewith the remedy (scope the mirror tomirror.https://rubygems.org, or unsetmirror.all). It should also exclude that gem from the same-run VEX, as it already does forredirect_gem_bundle_gemfile_unsupportedand stale installs. ABUNDLE_MIRROR__ALLset only in CI can't be seen at scan time, so that limitation belongs in the docs.redirected: 1, and in-run VEXnot_affected. The next install is silently unpatched (no CHECKSUMS), or fails with exit 37 and a remedy that leads to the unpatched install (CHECKSUMS).OS × version
not_affected).bundle/configand the env, so this is OS-independentFirst bad version: not bisected. Mirror settings have never been read anywhere in
crates/(grep -ri mirrorfinds no Bundler handling), so this has been present since the hosted gem mode shipped.Suspect code
crates/socket-patch-core/src/hosted/engine.rs:532(keep_bundler_loaded_gem_files) reads the project's bundler config only forBUNDLE_GEMFILE. That's the natural place to also detectBUNDLE_MIRROR__ALL(and aBUNDLE_MIRROR__<patch-registry host>key) from the app config and the env, honoringBUNDLE_IGNORE_CONFIG.crates/socket-patch-core/src/crawlers/ruby_crawler.rs:1169(bundle_config_setting) is the existing flat-YAML reader to reuse.crates/socket-patch-core/src/patch/redirect/mod.rs:5405(rewrite_gem) is where the source block is written.No probe runs: the reproduction is Linux-only because the defect is in config reading, not in platform paths.