Add --no-build-extension and --no-install-plugin options to gem install#9473
Add --no-build-extension and --no-install-plugin options to gem install#9473
--no-build-extension and --no-install-plugin options to gem install#9473Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds opt-out controls to RubyGems and Bundler to reduce supply-chain risk during installation by allowing users to skip native extension builds and RubyGems plugin installation/loading when explicitly requested.
Changes:
- Add
--[no-]build-extensionand--[no-]install-pluginoptions to RubyGems install/update option parsing and propagate them through dependency installation. - Update
Gem::Installer(and Bundler’s RubyGems installer wrapper) to conditionally skip extension builds and plugin installation/loading, with warnings and recovery guidance. - Add test coverage in RubyGems (Minitest) and Bundler (RSpec) for
no_build_extension.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| test/rubygems/test_gem_installer.rb | Adds RubyGems installer tests for skipping extension builds and plugin installation/loading. |
| spec/install/gems/no_build_extension_spec.rb | Adds Bundler spec verifying no_build_extension prevents extension build artifacts. |
| lib/rubygems/request_set.rb | Skips rebuilding extensions for already-installed specs when :build_extension == false. |
| lib/rubygems/installer.rb | Gates extension builds, plugin generation, and plugin loading; adds warning helpers; avoids loading missing plugin wrappers. |
| lib/rubygems/install_update_options.rb | Defines the new CLI flags for install/update commands. |
| lib/rubygems/dependency_installer.rb | Stores and forwards new options into the request set install flow. |
| bundler/lib/bundler/source/rubygems.rb | Passes Bundler settings (no_build_extension, no_install_plugin) into RubyGems installer options. |
| bundler/lib/bundler/source/path/installer.rb | Skips building extensions for path installs when no_build_extension is set. |
| bundler/lib/bundler/settings.rb | Registers no_build_extension and no_install_plugin as boolean settings. |
| bundler/lib/bundler/rubygems_gem_installer.rb | Mirrors RubyGems installer gating behavior for Bundler’s installer wrapper. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if options[:build_extension] == false | ||
| warn_skipped_extensions | ||
| else | ||
| build_extensions | ||
| end |
| wrappers: true, | ||
| env_shebang: true, | ||
| build_args: options[:build_args], | ||
| bundler_extension_cache_path: extension_cache_path(spec) | ||
| bundler_extension_cache_path: extension_cache_path(spec), | ||
| build_extension: Bundler.settings[:no_build_extension] ? false : nil, | ||
| install_plugin: Bundler.settings[:no_install_plugin] ? false : nil |
| sorted_requests.each do |req| | ||
| if req.installed? && @always_install.none? {|spec| spec == req.spec.spec } | ||
| req.spec.spec.build_extensions | ||
| req.spec.spec.build_extensions unless options[:build_extension] == false | ||
| yield req, nil if block_given? |
| generate_bin | ||
| generate_plugins | ||
| if options[:install_plugin] == false | ||
| warn_skipped_plugins | ||
| else | ||
| generate_plugins | ||
| end |
| if options[:install_plugin] == false | ||
| warn_skipped_plugins | ||
| else | ||
| generate_plugins | ||
| end |
…nstall These options allow users to opt out of building native extensions and installing plugins during gem installation, providing an equivalent to npm's --ignore-scripts for mitigating arbitrary code execution vectors. Both options default to true to maintain backward compatibility. Users can disable them per-command or globally via gemrc configuration. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When plugins are not installed (e.g. via --no-install-plugin), the plugin files do not exist on disk. Without this check, load_plugin would attempt to load non-existent files and produce spurious LoadError warnings. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extend the --no-build-extension and --no-install-plugin support to Bundler's installation paths. RubyGemsGemInstaller#install now respects these options, and the settings are propagated from Bundler::Settings through Source::RubyGems to the installer. Path::Installer also respects no_build_extension for git/path sources. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lugins When extensions or plugins are skipped via --no-build-extension or --no-install-plugin, warn the user and point them to the appropriate gem pristine command to re-enable later. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Without this change, reinstalling or upgrading a gem with --no-install-plugin would still execute a pre-existing plugin wrapper left by a previous install via load_plugin. This defeats the opt-out. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…config The quality spec requires all Bundler settings to be documented in the bundle-config man page. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a gem upgrades from a version with plugins to one without, generate_plugins normally removes the old wrapper files. Skipping generate_plugins entirely with --no-install-plugin prevented this cleanup, leaving stale wrappers that would still be loaded. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources\. Defaults to true\. | ||
| .TP | ||
| \fBno_build_extension\fR (\fBBUNDLE_NO_BUILD_EXTENSION\fR) | ||
| Whether Bundler should skip building native extensions during installation\. When set, gems are installed without compiling their C extensions\. To build extensions later, run \fBgem pristine <gem> \-\-extensions\fR\. |
There was a problem hiding this comment.
I think the gem pristine <gem> command is not going to work if a user has set BUNDLE_PATH (either in env or config). Wouldn't running bundle install again build the extensions ?
There was a problem hiding this comment.
👍 Your concern is correct. I updated the man page and the warnings now advise unsetting the setting and running bundle pristine <gem>.
Gem::Resolver::GitSpecification#install calls Gem::Installer#build_extensions directly, so the guard at the main install path did not cover git sources. Move the options check into build_extensions itself so every caller skips the build and emits the same warning when the option is set. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The existing spec only checked that the wrapper is skipped on a fresh install. Add a version upgrade case so that when a later version of the gem no longer ships plugins, the previously generated wrapper is removed even though no_install_plugin is set. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
gem pristine does not reach gems that Bundler installed under BUNDLE_PATH, so the guidance emitted when no_build_extension or no_install_plugin is set needs a Bundler-native equivalent. Override warn_skipped_extensions and warn_skipped_plugins in RubyGemsGemInstaller so they advise unsetting the matching Bundler setting and running bundle pristine, and update the bundle-config man page to match. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Background
Installing a gem can execute arbitrary code through two mechanisms: native extension builds (via
extconf.rb,Rakefile, etc.) and plugin evaluation (viarubygems_plugin.rb). This is a known supply chain attack vector, analogous to npm's postinstall scripts.This PR adds
--no-build-extensionand--no-install-pluginoptions togem install, allowing users to opt out of these behaviors. Bundler also gains correspondingno_build_extensionandno_install_pluginconfiguration settings.When extensions or plugins are skipped, a warning message is displayed with recovery instructions pointing to
gem pristine --extensionsorgem pristine --only-plugins, so users can re-enable them later without reinstalling the gem.These options only take effect when explicitly specified by the user. The default behavior remains unchanged: extensions are built and plugins are installed as before.
Making
--no-build-extensionthe default would be a significant behavioral change that requires careful investigation into how many gems in the ecosystem rely on native extensions being built at install time, how tools like bundler and CI pipelines depend on this behavior, and what the migration path would look like for gem authors.That analysis is outside the scope of this PR.
Make sure the following tasks are checked