Skip to content

Commit 1b9f1e1

Browse files
hsbtclaude
andcommitted
Keep lockfile parsing from loading Bundler source plugins
A PLUGIN SOURCE section sends Bundler::Plugin.from_lock looking for the plugin that handles it, and finding one loads and runs that plugin's `plugins.rb`. The plugin index is read from the lockfile directory, so a repository could run code just by being installed from with `gem install -g`. Nothing here can use a plugin source anyway. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent b2177cd commit 1b9f1e1

2 files changed

Lines changed: 72 additions & 0 deletions

File tree

lib/rubygems/request_set.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,14 @@ def load_lockfile(lock_file) # :nodoc:
348348
Bundler.instance_variable_set(:@root, Pathname.new(File.expand_path(File.dirname(lock_file))))
349349
root_swapped = true
350350

351+
# A PLUGIN SOURCE section otherwise sends Bundler::Plugin.from_lock looking
352+
# for the plugin that handles it, which loads and runs that plugin's
353+
# `plugins.rb`. Nothing here can use a plugin source anyway, so borrow the
354+
# flag Bundler itself uses to keep lockfile parsing inert.
355+
previous_gemfile_parse = Bundler::Plugin.instance_variable_get(:@gemfile_parse)
356+
Bundler::Plugin.instance_variable_set(:@gemfile_parse, true)
357+
gemfile_parse_swapped = true
358+
351359
parser = Bundler::LockfileParser.new(File.read(lock_file), lockfile_path: lock_file)
352360

353361
locked_versions = {}
@@ -410,6 +418,7 @@ def load_lockfile(lock_file) # :nodoc:
410418
end
411419
ensure
412420
Bundler.instance_variable_set(:@root, previous_root) if root_swapped
421+
Bundler::Plugin.instance_variable_set(:@gemfile_parse, previous_gemfile_parse) if gemfile_parse_swapped
413422
end
414423

415424
def pretty_print(q) # :nodoc:

test/rubygems/test_gem_request_set.rb

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,69 @@ def test_load_gemdeps_with_lockfile_path_section_newer_than_lockfile
549549
assert_equal %w[a-2], vendor_set.find_all(dep("a", "= 2")).map(&:full_name)
550550
end
551551

552+
def test_load_lockfile_does_not_load_plugins_for_a_plugin_source_section
553+
require "bundler"
554+
require "bundler/plugin"
555+
556+
plugin = File.join @tempdir, ".bundle", "plugin", "plugins", "example"
557+
loaded = File.join @tempdir, "plugin-was-loaded"
558+
559+
FileUtils.mkdir_p File.join(plugin, "lib")
560+
561+
File.open File.join(@tempdir, "Gemfile"), "w" do |io|
562+
io.puts 'source "https://rubygems.org"'
563+
end
564+
565+
File.open File.join(plugin, "plugins.rb"), "w" do |io|
566+
io.puts "File.write #{loaded.dump}, \"loaded\""
567+
io.puts "class ExampleSource"
568+
io.puts " include Bundler::Plugin::API::Source"
569+
io.puts "end"
570+
io.puts 'Bundler::Plugin::API.source("example_type", ExampleSource)'
571+
end
572+
573+
File.open File.join(@tempdir, ".bundle", "plugin", "index"), "w" do |io|
574+
io.puts <<~INDEX
575+
---
576+
commands:
577+
hooks:
578+
load_paths:
579+
example:
580+
- plugins/example/lib
581+
plugin_paths:
582+
example: plugins/example
583+
sources:
584+
example_type: example
585+
INDEX
586+
end
587+
588+
File.open "gem.deps.rb.lock", "w" do |io|
589+
io.puts <<~LOCKFILE
590+
PLUGIN SOURCE
591+
remote: https://gems.example/
592+
type: example_type
593+
specs:
594+
a (1)
595+
596+
PLATFORMS
597+
#{Gem::Platform::RUBY}
598+
599+
DEPENDENCIES
600+
a!
601+
LOCKFILE
602+
end
603+
604+
Bundler::Plugin.reset!
605+
606+
rs = Gem::RequestSet.new
607+
rs.load_lockfile "gem.deps.rb.lock"
608+
609+
assert_path_not_exist loaded
610+
assert_equal [dep("a")], rs.dependencies
611+
ensure
612+
Bundler::Plugin.reset!
613+
end
614+
552615
def test_load_lockfile_keeps_bundler_root_when_it_cannot_be_swapped
553616
require "bundler"
554617

0 commit comments

Comments
 (0)