Skip to content

Hosted gem stale-install warning calls the project's own vendor/bundle a "shared gem home" when --cwd is left at its default (or relative), so it gives the wrong remedy and drops the committed cache archive from the delete list #729

Description

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

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

    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