Skip to content

Gem hosted stale-install guard only checks vendor/cache, so a committed cache at a configured cache_path gets no warning, the in-run VEX attests it, and Bundler 2.4 installs the unpatched gem #483

Description

[agent] Found by the scheduled Bundler (RubyGems) bug-hunt routine (ledger #316).

Summary

The hosted gem stale-install guard (CLI_CONTRACT.md, "Gem stale-install guard") looks for the committed bundle cache archive only at <cwd>/vendor/cache/<name>-<version>.gem. Bundler's cache directory is configurable: bundle config set --local cache_path vendor/gems writes BUNDLE_CACHE_PATH into the committed .bundle/config, and BUNDLE_CACHE_PATH can also come from the environment. When a project commits its cache at that configured path:

  • scan --mode hosted emits no redirect_gem_stale_install warning.
  • The stale purl is therefore not added to stale_purls, so the same run's --vex attests not_affected … (redirected).
  • A fresh checkout's bundle install installs from the stale cached .gem:
    • Bundler 2.4.22 does this silently: exit 0, unpatched bytes.
    • Bundler 2.6.9 and 4.0.17 exit 37 ("mismatched checksums"), and the scan has given no remedy.

Impact

On Bundler ≤ 2.5, the vulnerable upstream code is installed and runs, while the scan reported a successful redirect and its VEX says not_affected. On newer bundlers every fresh install fails, and nothing points at the cause. The folded flavor has the same gap: when there's a stale installed dir, the delete-list remedy leaves the configured cache archive behind, which reinstates the stale bytes.

Repro

This uses the hermetic fixture from e2e_redirect_gem_build.rs (mock upstream + patch registry + API), which I kept running so I could drive the steps by hand.

mkdir proj && cd proj
bundle config set --local path vendor/bundle
bundle config set --local cache_path vendor/gems        # committed in .bundle/config
printf 'source "%s/upstream"\n\ngem "vuln-gem"\n' "$MOCK" > Gemfile
bundle install && bundle cache                          # vendor/gems/vuln-gem-1.0.0.gem (upstream bytes)
# fresh checkout: Gemfile, Gemfile.lock, .bundle/config, vendor/gems/ only
socket-patch scan --mode hosted --json --yes --cwd "$PWD" --api-url "$MOCK" \
  --org test-org --api-token fake --vex out.vex.json --vex-product pkg:gem/app@1.0.0
#   exit 0; redirect.warnings has no redirect_gem_stale_install
#   out.vex.json: GHSA-… not_affected "Patched via Socket patch <uuid> (redirected)"
bundle install    # (another fresh copy of the committed files)
#   Bundler 2.4.22: "Bundle complete!" exit 0; vendor/bundle/.../vuln-gem-1.0.0/lib/vuln_gem.rb == UPSTREAM bytes
#   Bundler 2.6.9 / 4.0.17: exit 37 "Bundler found mismatched checksums"

Control: the same steps with the default vendor/cache produce the documented redirect_gem_stale_install ("the project's committed bundler cache still holds an UNPATCHED archive at …/vendor/cache/vuln-gem-1.0.0.gem"), and the in-run VEX withholds the attestation.

Expected vs actual

  • Expected (CLI_CONTRACT.md "Gem stale-install guard"): "a committed vendor/cache archive whose sha256 differs from the patched artifact's warns standalone even with no installed dir at all", and "A stale-flagged purl is additionally excluded from the same run's --vex assume_applied set — the envelope must never attest a CVE its own warning says is live." The archive bundler actually installs from is the one at its configured cache path (Bundler.app_cache = settings.app_cache_path, which defaults to vendor/cache). The guard should resolve that path from BUNDLE_CACHE_PATH in .bundle/config or the environment.
  • Actual: the guard is silent, the VEX attests, and the unpatched gem is installed.

Matrix (Linux, Ruby 3.3.6, main 6e7ef74; fresh checkout with the committed cache, no installed dir)

Bundler cache_path: vendor/gems: warning in-run VEX fresh bundle install default vendor/cache (control)
2.4.22 none not_affected exit 0, unpatched bytes warns, VEX withheld
2.6.9 none not_affected exit 37 untested
4.0.17 (CHECKSUMS) none not_affected exit 37 warns

I reproduced each failing cell at least twice. The path logic doesn't depend on the OS, so macOS and Windows weren't probed.

Suspect code

  • crates/socket-patch-cli/src/commands/scan/hosted.rs:422: the folded flavor, cwd.join("vendor").join("cache").
  • crates/socket-patch-cli/src/commands/scan/hosted.rs:471: the standalone flavor (pass 3), with the same hard-coded path.

No probe runs.

Activity

  1. mikolalysenko commented on Oct 1, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Triaged as priority:p1 (Bundler). Confirmed on main: both guard flavors in crates/socket-patch-cli/src/commands/scan/hosted.rs (around lines 423 and 472) hard-code <cwd>/vendor/cache, and neither resolves BUNDLE_CACHE_PATH from .bundle/config or the environment. No open or merged PR covers this.


    Generated by Claude Code

  2. mikolalysenko commented on Oct 2, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Claiming this issue (with #507; shared root cause: socket-patch reads Bundler settings ad hoc instead of through one resolver with Bundler's priority, local app config over ENV. gemfile uses the wrong order, and cache_path isn't read at all). Branch: agent/fix-bundler-settings-resolution. Claim-ID: 2026-10-02T05:21:03Z-66881b


    Generated by Claude Code

  3. mikolalysenko commented on Oct 2, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Draft fix PR: #532


    Generated by Claude Code

  4. mikolalysenko commented on Oct 2, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Bundler bug-hunt (ledger #316): I verified draft PR #532 (head 477aae9) against a real-Bundler repro of both arms.

    The setup is the hosted fixture with a CHECKSUMS lock and Bundler 4.0.17. I ran bundle cache into pkgcache, removed vendor/bundle, then ran scan --mode hosted:

    Arm main 61cfb9b PR #532
    BUNDLE_CACHE_PATH=pkgcache from the environment no warning; next install exits 37 (checksum mismatch) on 4.0.17, and installs unpatched "VULNERABLE" silently on 2.4.22 redirect_gem_stale_install
    bundle config set --local cache_path pkgcache no warning redirect_gem_stale_install

    PR #532 fixes both arms on this repro.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent:triagedbugSomething isn't workingbughuntFound by a scheduled package-manager bug-hunt agentpm:bundlerBundler (RubyGems)priority:p1

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions