Skip to content

Hosted gem redirect ignores Bundler's mirror.all setting, so the next bundle install fetches the redirected gem's upstream bytes from the mirror while the in-run VEX attests not_affected #681

Description

[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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions