[agent] Found by the scheduled Bundler (RubyGems) bug-hunt routine (ledger #316).
Summary
scan --mode hosted (and get --mode hosted) picks the flavor of redirect_gem_stale_install with a lexical gem_dir.starts_with(cwd). --cwd defaults to . (SOCKET_CWD), while the crawler hands back gem dirs as vendor/bundle/ruby/3.3.0/gems/<leaf>. Path::starts_with compares components, and vendor/… doesn't start with .. So in the default invocation, run from the project root with no --cwd, a stale install in the project's own vendor/bundle always gets the shared gem home flavor:
…a stale UNPATCHED install is materialized in the shared gem home at vendor/bundle/ruby/3.3.0/gems/colorize-0.8.1 … That gem home is shared by every project on this machine: prefer switching this project to a project-local bundle path (bundle config set --local path vendor/bundle, then bundle install); remove … directly only if no other project relies on the stale gem
The same starts_with(cwd) gate (crates/socket-patch-cli/src/commands/scan/hosted.rs:423) decides whether the project's committed <cache_path>/<leaf>.gem gets folded into the delete list. With a relative cwd it never does: the cache archive is reported as a separate warning, and the installed-dir warning's delete list leaves it out.
Impact
- The prescribed remedy does nothing. The project is already on
path vendor/bundle, so running bundle config set --local path vendor/bundle && bundle install prints Using colorize 0.8.1 and the upstream (vulnerable) bytes stay installed. I verified this below.
- The one remedy that works (deleting the three paths) is framed as risky ("only if no other project relies on the stale gem"), which nudges users away from it.
- This is the default way to run the CLI. Only an absolute
--cwd (or a relative one with a .. component) gets the correct project-local wording.
- VEX is not affected: the stale purl is excluded from
assume_applied in both flavors.
Repro (Linux, Ruby 3.3.6, Bundler 4.0.17; a mock patch API on loopback serves a rebuilt colorize-0.8.1 with a marker line; the setup is described in ledger #316, run 13)
mkdir proj && cd proj
printf 'source "https://rubygems.org"\n\ngem "colorize", "0.8.1"\n' > Gemfile
bundle config set --local path vendor/bundle
bundle install # stale upstream copy in vendor/bundle
bundle lock --add-checksums
socket-patch scan --json --yes --api-url $MOCK --org org --api-token fake > a.json # default --cwd "."
jq -r '.redirect.warnings[].detail' a.json # -> "…in the shared gem home at vendor/bundle/…"
bundle config set --local path vendor/bundle && bundle install # follow the remedy
grep -c SOCKET_PATCHED vendor/bundle/ruby/3.3.0/gems/colorize-0.8.1/lib/colorize.rb # -> 0 (still unpatched)
# control: identical project, absolute --cwd
socket-patch scan --json --yes --cwd "$PWD" … # -> "…is already materialized at /abs/…/vendor/bundle/… Remove the stale materialization — …"
rm -rf <the three listed paths> && bundle install # -> Installing colorize 0.8.1, marker present (1)
Expected vs actual
- Expected (CLI_CONTRACT.md, "Gem stale-install guard"): "a PROJECT-LOCAL dir gets the verified delete-list remedy (installed dir, cache
.gem, specifications entry — plus the project's committed <cache dir>/<leaf>.gem when present …); a SHARED gem-env home gets a caveat…". vendor/bundle under the project root is project-local whatever spelling --cwd has.
- Actual: the flavor depends on how
--cwd is spelled. . and the default get the shared-home caveat plus an ineffective remedy, and the committed cache archive isn't folded into the delete list.
Matrix (main 045d7ec; also the published v4.0.0 binary)
--cwd |
Bundler 4.0.17 |
Bundler 2.6.9 |
Bundler 2.4.22 |
v4.0.0 release (4.0.17) |
omitted (default .) |
shared (wrong) |
shared |
shared |
shared |
. |
shared |
shared |
shared |
— |
../proj |
local |
local |
local |
— |
| absolute |
local |
local |
local |
local |
omitted + committed vendor/cache |
shared + separate cache warning (archive not in the delete list) |
same |
same |
— |
OS: reproduced on Linux. The comparison is lexical (Path::starts_with) and --cwd defaults to . on every platform, so macOS and Windows should behave the same (not probed).
First bad release: present in v4.0.0, the latest release, so it isn't a regression on main.
Suspect code
crates/socket-patch-cli/src/commands/scan/hosted.rs:218: let detail = if gem_dir.starts_with(cwd) {
crates/socket-patch-cli/src/commands/scan/hosted.rs:423: if dir.starts_with(cwd) { (cache folding)
Open PR #712 replaces the first one with a project_local bool, but it still computes it as dir.starts_with(cwd) against the raw cwd, so it doesn't fix this. Normalizing both sides (absolutize cwd against the process cwd, as the crawler's own containment guard does) would.
[agent] Found by the scheduled Bundler (RubyGems) bug-hunt routine (ledger #316).
Summary
scan --mode hosted(andget --mode hosted) picks the flavor ofredirect_gem_stale_installwith a lexicalgem_dir.starts_with(cwd).--cwddefaults to.(SOCKET_CWD), while the crawler hands back gem dirs asvendor/bundle/ruby/3.3.0/gems/<leaf>.Path::starts_withcompares components, andvendor/…doesn't start with.. So in the default invocation, run from the project root with no--cwd, a stale install in the project's ownvendor/bundlealways gets the shared gem home flavor:The same
starts_with(cwd)gate (crates/socket-patch-cli/src/commands/scan/hosted.rs:423) decides whether the project's committed<cache_path>/<leaf>.gemgets folded into the delete list. With a relativecwdit never does: the cache archive is reported as a separate warning, and the installed-dir warning's delete list leaves it out.Impact
path vendor/bundle, so runningbundle config set --local path vendor/bundle && bundle installprintsUsing colorize 0.8.1and the upstream (vulnerable) bytes stay installed. I verified this below.--cwd(or a relative one with a..component) gets the correct project-local wording.assume_appliedin both flavors.Repro (Linux, Ruby 3.3.6, Bundler 4.0.17; a mock patch API on loopback serves a rebuilt
colorize-0.8.1with a marker line; the setup is described in ledger #316, run 13)Expected vs actual
.gem,specificationsentry — plus the project's committed<cache dir>/<leaf>.gemwhen present …); a SHARED gem-env home gets a caveat…".vendor/bundleunder the project root is project-local whatever spelling--cwdhas.--cwdis spelled..and the default get the shared-home caveat plus an ineffective remedy, and the committed cache archive isn't folded into the delete list.Matrix (main
045d7ec; also the published v4.0.0 binary)--cwd.).../projvendor/cacheOS: reproduced on Linux. The comparison is lexical (
Path::starts_with) and--cwddefaults to.on every platform, so macOS and Windows should behave the same (not probed).First bad release: present in v4.0.0, the latest release, so it isn't a regression on main.
Suspect code
crates/socket-patch-cli/src/commands/scan/hosted.rs:218:let detail = if gem_dir.starts_with(cwd) {crates/socket-patch-cli/src/commands/scan/hosted.rs:423:if dir.starts_with(cwd) {(cache folding)Open PR #712 replaces the first one with a
project_localbool, but it still computes it asdir.starts_with(cwd)against the rawcwd, so it doesn't fix this. Normalizing both sides (absolutizecwdagainst the process cwd, as the crawler's own containment guard does) would.