Repository navigation
Hosted gem VEX attests not_affected for an unpatched install when .bundle/config sets an out-of-tree path (absolute or ~/…), because the skipped bundle root counts as "nothing installed" #709
Description
Activity
- addedbugSomething isn't workingSomething isn't workingbughuntFound by a scheduled package-manager bug-hunt agentFound by a scheduled package-manager bug-hunt agentpm:bundlerBundler (RubyGems)Bundler (RubyGems)
on Oct 3, 2026 mikolalysenko commented
on Oct 3, 2026 CollaboratorAuthorMore actions[agent] Cross-OS confirmation from probe run https://github.com/SocketDev/socket-patch/actions/runs/37137283662 (main
045d7ec, Bundler 4.0.17, the same mock as above)..bundle/configpathwas set to$RUNNER_TEMP/outside bundle, i.e. outside the project and containing a space.Runner Ruby config BUNDLE_PATHscan warnings in-run VEX installed copy patched? post-install vexwindows-latest 3.3 D:/a/_temp/outside bundlegem_bundle_config_path_ignoredonly1 statement no exit 0, not_affectedwindows-latest 3.4 same same 1 statement no exit 0, not_affectedmacos-latest 3.3 /Users/runner/work/_temp/outside bundlesame 1 statement no exit 0, not_affectedmacos-latest 3.4 same same 1 statement no exit 0, not_affectedSo it reproduces on all three OSes, including a Windows drive-letter path.
Generated by Claude Code
- added a commit that references this issue
on Oct 3, 2026 mikolalysenko commented
on Oct 3, 2026 CollaboratorAuthorMore actions[agent] Triaged
priority:p1(Bundler). Confirmed against main045d7ec. The ruby crawler refuses a config-sourcedBUNDLE_PATHoutside the project root (resolve_config_bundle_path) and never probes it. The hosted lockfile-basis excuse incrates/socket-patch-cli/src/commands/vex.rs(~L581-600) then accepts thatpackage_not_foundas "nothing installed". Its guard only excludes purls that--ecosystemsscoped out. It does not exclude install roots the crawler declined to look in.Not a duplicate, and no open or merged PR touches it. Related but not clustered: #686 (Composer absolute or
~vendor-dir) ends in the same vex excuse, but the discovery fix sits in a different crawler (composer_crawler.rs::resolve_local_vendor_dir, already clustered with #439). A shared fail-closed rule in vex for "the crawler skipped a configured root" would cover both. Whoever fixes either one should consider doing it at that boundary.
Generated by Claude Code
mikolalysenko commented
on Oct 3, 2026 CollaboratorAuthorMore actions[agent] Claiming this issue (shared root cause: hosted gem verification never probes a .bundle/config path outside the project, so stale-install and VEX treat the gem as not installed). Branch: agent/fix-gem-config-path-verification. Claim-ID: 2026-10-03T18:20:52Z-60d5bd
Generated by Claude Code
mikolalysenko commented
on Oct 3, 2026 CollaboratorAuthorMore actions- added 4 commits that reference this issue
on Oct 3, 2026
[agent] Found by the scheduled Bundler (RubyGems) bug-hunt routine (ledger #316).
Summary
bundle config set --local path <dir>with<dir>outside the project (an absolute path such as/opt/bundleor/tmp/x, or~/.bundle-store) is an ordinary Bundler setup. The ruby crawler deliberately refuses a config-sourcedBUNDLE_PATHoutside the project root, because that root is also an apply write target (resolve_config_bundle_path, containment guard). For hosted mode, though, that refusal means:redirect_gem_stale_install.scan --mode hosted --vexattests the redirected gemnot_affected.bundle installprintsUsing colorize 0.8.1and keeps the unpatched bytes, because they're already in the configured path (the case the stale guard exists for).vex(default verify mode) also attestsnot_affected(redirected). It finds no installed copy (it never looked in the configured root) and falls into the "absence of any installed copy is excused / lockfile basis" branch.vexprints no warning at all.scanprints a run-levelgem_bundle_config_path_ignoredwarning, but that warning only says the root is ignored "as an install root", not that the redirect and VEX are unverified.The comment at
crates/socket-patch-cli/src/commands/vex.rs:588says ""Absent" must mean the crawler LOOKED". It handles--ecosystemsscoping, but a bundle root the crawler refused to look in also counts as "absent".Impact
A false
not_affectedVEX statement for code that is still vulnerable and loaded at runtime (bundle execloads<path>/ruby/3.3.0/gems/colorize-0.8.1/lib). This holds both in the in-run attestation and in the post-install verified attestation that's meant to catch exactly this. An env-sourcedBUNDLE_PATHpointing at the same directory is handled correctly (stale warning, andvexrefuses withnot_applied), so only the.bundle/configspelling is affected.Repro (Linux, Ruby 3.3.6, real rubygems.org upstream, local mock of the patch API + patch registry)
The mock serves
/v0/orgs/org/patches/{batch,by-package,package,view}forpkg:gem/colorize@0.8.1and a compact index at/patch-registry/gem/tok/<uuid>/serving a rebuiltcolorize-0.8.1.gemwhoselib/colorize.rbends in# SOCKET_PATCHED. That's the same shape as the fixture ine2e_redirect_gem_build.rs.Expected vs actual
bundle installwould reuse is flaggedredirect_gem_stale_install, 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 post-installvexshould refuse (not_applied), as it does for the same directory supplied through envBUNDLE_PATH. At the very least, when the crawler skipped a configured bundle root it shouldn't treat the gem as "not installed" for the lockfile-basis excuse, andvexshould say so. The containment guard protects writes; reading the root to verify or to detect staleness doesn't write anything.not_affectedwhile Bundler installs and loads the unpatched gem.Matrix (Linux, Ruby 3.3.6; every cell ends in a real
bundle install)pathabsolute, outside the projectpath~/.bh-bundlepathrelativevendor/bundlevexrefusesnot_applied)pathabsolute, inside the projectBUNDLE_PATHabsolute, outside the projectmacOS / Windows: untested, but the containment check is lexical and OS-independent. On Windows, a drive-letter path to another folder (
C:\bundle) would take the same branch.First bad version: not bisected. The containment guard and the lockfile-basis VEX excuse both predate this ledger.
Suspect code
crates/socket-patch-core/src/crawlers/ruby_crawler.rs:341: the refused config root is recorded asskipped_config_pathand never probed (resolve_config_bundle_path,:1092).crates/socket-patch-cli/src/commands/vex.rs:581-600: the hosted lockfile-basis excuse applies to a gem whose bundle root was skipped, andvexnever surfacesgem_bundle_config_path_ignored(onlyscan/mod.rs:1602andapply.rs:1813do).Related: #686 is the Composer twin (absolute or
~config.vendor-dir). #577 is a different missing layer (Bundler's global config).