[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).
[agent] Found by the scheduled Bundler (RubyGems) bug-hunt routine (ledger #316).
Summary
socket-patch setupaccepts every Bundler from 2.2 up (MIN_BUNDLER = (2, 2)). The plugin it generates promises that abundle pristinereversion is re-patched in the same run: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 runsbundle installagain.Root cause (Bundler-side, confirmed in its source): Bundler 2.4.22
lib/bundler/cli/pristine.rb:48reinstalls withBundler::GemInstaller.new(spec, installer, false, 0, true).install_from_spec. That path never callsPlugin.hook. OnlyParallelInstaller#do_installfiresGEM_AFTER_INSTALL(installer/parallel_installer.rb:138). Bundler 2.5 switched pristine toParallelInstaller.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 pristinesilently brings the vulnerable code back.bundle execthen loads it with no warning, because nothing fires onbundle exec.setup --checkdoes report1 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.rskept alive), serving gemvuln-gem 1.0.0and a free patch forlib/vuln_gem.rb. The steps, withV=2.4.22:Expected vs actual
plugins.rb.tmpl:21-22, 290-296): afterbundle pristine, the per-gemafter-installhook re-applies the patch in the same run on every Bundler thatsetupaccepts (docs/ecosystems.md: "needs bundler ≥ 2.2"). Failing that, the limitation should be documented next to the existingbundle exec/gem pristinecaveat inplugins.rb.tmpl:26-28.bundle install.Matrix (Linux, Ruby 3.3.6,
force_ruby_platform,BUNDLE_PATH=vendor/bundle)setupbundle pristinekeeps patchsetup --removebyte-exactgems.rb)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
setupprint 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'spristine.rb).Tested on main
f6b7fb9(v4.0.0).