Repository navigation
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 hostedemits noredirect_gem_stale_installwarning.- The stale purl is therefore not added to
stale_purls, so the same run's--vexattestsnot_affected … (redirected). - A fresh checkout's
bundle installinstalls 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/cachearchive 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--vexassume_appliedset — 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 tovendor/cache). The guard should resolve that path fromBUNDLE_CACHE_PATHin.bundle/configor 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
bundler: run 5 ledger (#482, #483)
mikolalysenko commentedon Oct 1, 2026
[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 resolvesBUNDLE_CACHE_PATHfrom.bundle/configor the environment. No open or merged PR covers this.Generated by Claude Code
mikolalysenko commentedon Oct 2, 2026
[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.
gemfileuses the wrong order, andcache_pathisn't read at all). Branch: agent/fix-bundler-settings-resolution. Claim-ID: 2026-10-02T05:21:03Z-66881bGenerated by Claude Code
mikolalysenko commentedon Oct 2, 2026
[agent] Draft fix PR: #532
Generated by Claude Code
Read Bundler settings in Bundler's priority
Check the configured bundle cache for stale gems
mikolalysenko commentedon Oct 2, 2026
[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 cacheintopkgcache, removedvendor/bundle, then ranscan --mode hosted:61cfb9bBUNDLE_CACHE_PATH=pkgcachefrom the environment"VULNERABLE"silently on 2.4.22redirect_gem_stale_installbundle config set --local cache_path pkgcacheredirect_gem_stale_installPR #532 fixes both arms on this repro.
Generated by Claude Code