Skip to content

Bundler plugin from setup does not re-apply gem patches after bundle pristine on Bundler 2.2–2.4, so the patch stays reverted until the next bundle install #389

Description

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

Summary

socket-patch setup accepts every Bundler from 2.2 up (MIN_BUNDLER = (2, 2)). The plugin it generates promises that a bundle pristine reversion is re-patched in the same run:

bundle pristine fires ONLY the per-gem after-install events, so that digest-gated hook is what catches pristine's patch reversion in the same run
— crates/socket-patch-core/src/setup/gem/templates/plugins.rb.tmpl:21-22, again at :292 and crates/socket-patch-core/src/setup/gem/mod.rs:877

That only holds from Bundler 2.5 on. On 2.2, 2.3 and 2.4, bundle pristine <gem> reinstalls the upstream bytes, exits 0, and the plugin never runs. The gem stays unpatched until someone runs bundle install again.

Root cause (Bundler-side, confirmed in its source): Bundler 2.4.22 lib/bundler/cli/pristine.rb:48 reinstalls with Bundler::GemInstaller.new(spec, installer, false, 0, true).install_from_spec. That path never calls Plugin.hook. Only ParallelInstaller#do_install fires GEM_AFTER_INSTALL (installer/parallel_installer.rb:138). Bundler 2.5 switched pristine to ParallelInstaller.call(...) (cli/pristine.rb:56), which is why 2.5+ heals. So the plugin can't catch it through hooks on 2.2–2.4. At minimum the contract comments are wrong, and the docs don't mention the gap.

Impact

On a supported Bundler (2.2–2.4), a developer or CI step running bundle pristine silently brings the vulnerable code back. bundle exec then loads it with no warning, because nothing fires on bundle exec. setup --check does report 1 patch is not applied on disk, but only if someone runs it.

Repro

A hermetic fixture: mock upstream compact index plus a mock patch API (a copy of crates/socket-patch-cli/tests/e2e_redirect_gem_build.rs kept alive), serving gem vuln-gem 1.0.0 and a free patch for lib/vuln_gem.rb. The steps, with V=2.4.22:

mkdir proj && cd proj
printf 'source "%s/upstream"\n\ngem "vuln-gem"\n' "$MOCK" > Gemfile
bundle _${V}_ config set --local path vendor/bundle
bundle _${V}_ install
socket-patch setup --yes
socket-patch get "$UUID" --yes --api-url "$MOCK" --org test-org --api-token x   # agent mode
bundle _${V}_ install                       # plugin registers + re-applies
grep -c PATCHED vendor/bundle/ruby/3.3.0/gems/vuln-gem-1.0.0/lib/vuln_gem.rb   # 1
bundle _${V}_ pristine vuln-gem             # "Installing vuln-gem 1.0.0", exit 0
grep -c PATCHED vendor/bundle/ruby/3.3.0/gems/vuln-gem-1.0.0/lib/vuln_gem.rb   # 0  <-- reverted, no hook ran
socket-patch setup --check                  # "1 patch is not applied on disk"
bundle _${V}_ install                       # the next install heals it
grep -c PATCHED …/lib/vuln_gem.rb           # 1

Expected vs actual

  • Expected (the plugin's own contract, plugins.rb.tmpl:21-22, 290-296): after bundle pristine, the per-gem after-install hook re-applies the patch in the same run on every Bundler that setup accepts (docs/ecosystems.md: "needs bundler ≥ 2.2"). Failing that, the limitation should be documented next to the existing bundle exec / gem pristine caveat in plugins.rb.tmpl:26-28.
  • Actual: on 2.2–2.4 the patch is reverted with exit 0, and it stays reverted until the next bundle install.

Matrix (Linux, Ruby 3.3.6, force_ruby_platform, BUNDLE_PATH=vendor/bundle)

Bundler setup install re-applies bundle pristine keeps patch fresh clone install patched setup --remove byte-exact
2.2.33 ok yes no yes yes
2.3.27 ok yes no yes yes
2.4.22 ok yes no (reproduced twice) yes yes
2.5.22 ok yes yes yes yes
4.0.17 ok yes yes (also with gems.rb) yes yes

The cause is in Bundler's own code path, not in the OS, so macOS and Windows should behave the same. They weren't probed.

Suggested direction

This can't be fixed with a hook on 2.2–2.4, because Bundler fires none there. Options: (a) document it (in the template's caveat list and in docs/ecosystems.md) and correct the "same run" claim to say 2.5+; (b) have setup print a note when the probed Bundler is below 2.5; or (c) also subscribe the plugin to a later event that 2.2–2.4 pristine does fire, if one exists (I found none in 2.4.22's pristine.rb).

Tested on main f6b7fb9 (v4.0.0).

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