{"id":"TECH-202611-e3mx3j","kind":"technique","aliases":[],"related":[],"title":"RubyGems runs a gem's code on install even without an install script, and gem plugins run again on every gem command (even gem list); stable RubyGems has no way to turn this off (Aikido)","description":null,"summary":"Aikido Security research (published 5 Nov 2026, updated 9 Nov): a Ruby gem can run code on your machine through native extension build files declared in its gemspec (extconf.rb, mkrf_conf.rb, Rakefile), which RubyGems runs during gem install / bundle install, and through lib/rubygems_plugin.rb, which loads at the end of the install and again on every later gem command, including gem list, gem search and gem -v. Stable RubyGems and Bundler 4.0.x offer no way to disable either; RubyGems/Bundler 4.1.0.beta3 adds opt-out settings that are off by default. Reported to RubyGems security on 27 Oct 2026. The extension path was used by the malicious strutil-core 1.0.1 gem (tracked separately); four old research-style gems that phone home on install (xmastree, ancient-city-ruby, hacky, et_phone_home) were still installable on 4 Nov. Any developer machine or CI job that installs gems is exposed. No CVE.","severity":"medium","cvss":null,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"RubyGems","fixed":[],"affected":"4.0.x (no opt-out); 4.1.0.beta3 opt-out off by default","ecosystem":"rubygems"},{"name":"Bundler","fixed":[],"affected":"4.0.x","ecosystem":"rubygems"}],"ecosystems":[],"tags":["early-warning","technique"],"not_affected_if":[],"how_to_check":"Grep Gemfile.lock for xmastree (0.0.1, 0.0.2), ancient-city-ruby (2013.1), hacky (0.0.2), et_phone_home (0.1.0). Network/DNS hits to cryptic-ocean-8847.herokuapp.com, stark-samurai-8122.herokuapp.com, appfolio.herokuapp.com/upload, 176.31.142.25. List installed gems without using gem commands: ruby -e 'Gem.path.each { |p| puts Dir.glob(\"#{p}/gems/*\") }'. List plugin dirs: ruby -e 'Gem.path.each { |p| puts Gem.plugindir(p) }' and look for unexpected rubygems_plugin.rb files. Review extconf.rb/mkrf_conf.rb/Rakefile in new or unfamiliar gems.","fix":"Remove any of the four gems (gem uninstall NAME --all --executables, drop from Gemfile) and remove unneeded plugins from the plugin dirs. To stop install-time code: gem update --system 4.1.0.beta3, then gem install --no-build-extension --no-install-plugin, bundle config set --global no_build_extension true and no_install_plugin true; in CI set BUNDLE_NO_BUILD_EXTENSION=true and BUNDLE_NO_INSTALL_PLUGIN=true (only skip extension builds in jobs that don't load the app). Restore needed extensions afterwards with gem pristine <gem> --extensions. Inspect new gems with gem fetch + gem unpack in a clean environment. Note: this only blocks install-time code; a malicious gem still runs when the app loads it.","status":"open","published_at":null,"first_seen_at":"2026-11-09T12:28:43+00:00","triaged_at":"2026-11-09T12:28:43+00:00","updated_at":"2026-11-09T12:30:02+00:00","category":"package","confidence":null,"triage":null,"analyst_status":"done","analyst_note":null,"search":"'-202611':37A '1.0.1':154B '2026':51B,142B '27':140B '4':182B '4.0':114B '4.1.0':123B '5':49B '9':53B 'add':125B 'aikido':35A,45B 'ancient':171B 'ancient-city-rubi':170B 'beta3':124B 'build':67B 'bundl':82B 'bundler':40A,113B 'ci':188B 'citi':172B 'code':6A,60B 'command':22A,101B 'core':153B 'cve':196B 'declar':69B 'default':134B 'develop':185B 'disabl':120B 'e3mx3j':38A 'earli':42B 'early-warn':41B 'either':121B 'end':91B 'et':175B 'even':9A,23A 'everi':20A,98B 'expos':194B 'extconf.rb':73B 'extens':66B,144B 'file':68B 'four':158B 'gem':4A,15A,21A,24A,57B,80B,100B,103B,105B,108B,155B,163B,192B 'gemspec':72B 'hacki':174B 'home':166B,177B 'includ':102B 'instal':8A,12A,81B,83B,94B,168B,180B,191B 'job':189B 'later':99B 'lib/rubygems_plugin.rb':86B 'list':25A,104B 'load':88B 'machin':63B,186B 'malici':150B 'mkrf_conf.rb':74B 'nativ':65B 'nov':50B,54B,183B 'oct':141B 'offer':116B 'old':159B 'opt':127B 'opt-out':126B 'path':145B 'phone':165B,176B 'plugin':16A 'publish':48B 'rakefil':75B 'report':135B 'research':47B,161B 'research-styl':160B 'rubi':56B,173B 'rubygem':1A,27A,77B,111B,137B 'rubygems':39A 'rubygems/bundler':122B 'run':2A,17A,59B,78B 'script':13A 'search':106B 'secur':46B,138B 'separ':157B 'set':129B 'stabl':26A,110B 'still':179B 'strutil':152B 'strutil-cor':151B 'style':162B 'tech':36A 'techniqu':44B 'track':156B 'turn':32A 'updat':52B 'use':147B 'v':109B 'warn':43B 'way':30A,118B 'without':10A 'x':115B 'xmastre':169B","nicknames":[],"mitigations":[],"who_affected":null,"tier":"C","verified":{},"analyst_revisit_at":null,"fix_status":null,"audience":null,"plain_action":null,"pkeys":["bundler","rubygems"],"ptokens":["bundler","rubygems"],"labels":["earlywarning","medium","package","technique"],"campaign_type":"supply-chain","search_a":"'-202611':37A 'aikido':35A 'bundler':40A 'code':6A 'command':22A 'e3mx3j':38A 'even':9A,23A 'everi':20A 'gem':4A,15A,21A,24A 'instal':8A,12A 'list':25A 'plugin':16A 'rubygem':1A,27A 'rubygems':39A 'run':2A,17A 'script':13A 'stabl':26A 'tech':36A 'turn':32A 'way':30A 'without':10A","search_b":"'1.0.1':154B '2026':51B,142B '27':140B '4':182B '4.0':114B '4.1.0':123B '5':49B '9':53B 'add':125B 'aikido':45B 'ancient':171B 'ancient-city-rubi':170B 'beta3':124B 'build':67B 'bundl':82B 'bundler':113B 'ci':188B 'citi':172B 'code':60B 'command':101B 'core':153B 'cve':196B 'declar':69B 'default':134B 'develop':185B 'disabl':120B 'earli':42B 'early-warn':41B 'either':121B 'end':91B 'et':175B 'everi':98B 'expos':194B 'extconf.rb':73B 'extens':66B,144B 'file':68B 'four':158B 'gem':57B,80B,100B,103B,105B,108B,155B,163B,192B 'gemspec':72B 'hacki':174B 'home':166B,177B 'includ':102B 'instal':81B,83B,94B,168B,180B,191B 'job':189B 'later':99B 'lib/rubygems_plugin.rb':86B 'list':104B 'load':88B 'machin':63B,186B 'malici':150B 'mkrf_conf.rb':74B 'nativ':65B 'nov':50B,54B,183B 'oct':141B 'offer':116B 'old':159B 'opt':127B 'opt-out':126B 'path':145B 'phone':165B,176B 'publish':48B 'rakefil':75B 'report':135B 'research':47B,161B 'research-styl':160B 'rubi':56B,173B 'rubygem':77B,111B,137B 'rubygems/bundler':122B 'run':59B,78B 'search':106B 'secur':46B,138B 'separ':157B 'set':129B 'stabl':110B 'still':179B 'strutil':152B 'strutil-cor':151B 'style':162B 'techniqu':44B 'track':156B 'updat':52B 'use':147B 'v':109B 'warn':43B 'way':118B 'x':115B 'xmastre':169B","search_c":"","cause_entry":null,"cause_path":null,"disclosures_checked_at":null,"first_public_at":"2026-11-05T00:00:00+00:00","kev":null,"cvss_vector":null,"cvss_by":null,"poc_count":0,"sources":[{"source":"analyst","external_id":"TECH-202611-e3mx3j","url":null,"severity":null,"published_at":null,"modified_at":null,"first_seen_at":"2026-11-09T12:28:43+00:00","fetched_at":"2026-11-09T12:28:43+00:00","note":null}],"documents":[{"id":107311,"url":"https://x.com/AikidoSecurity/status/2119762205277118492","title":"@AikidoSecurity: Our researcher Hunter found that a gem can run code on your machine without an i","kind":"post","source":"x","publisher":null,"text":"Our researcher Hunter found that a gem can run code on your machine without an install script. It's the kind of thing you'd never expect.\n\nRubyGems plugins even run again on every later gem command, like gem list. npm 12 made install scripts opt-in. RubyGems still runs extension\n\nlikes 9 reposts 1","published_at":"2026-11-09T12:01:07+00:00","relation":"describes","confidence":null,"note":null},{"id":107310,"url":"https://x.com/AikidoSecurity/status/2119762207638536680","title":"@AikidoSecurity: Aikido Device Protection checks gem install and bundle install on Ruby 3 and lat","kind":"post","source":"x","publisher":null,"text":"Aikido Device Protection checks gem install and bundle install on Ruby 3 and later against Aikido Intel, and blocks known malware before it touches the filesystem.\nhttps://t.co/RbIrcROdfR\n\nlikes 1 reposts 0","published_at":"2026-11-09T12:01:08+00:00","relation":"describes","confidence":null,"note":null},{"id":80704,"url":"https://www.aikido.dev/blog/gem-list-runs-someone-elses-code","title":"Even gem list can run someone else's code (Aikido)","kind":"blog","source":"blog","publisher":null,"text":"Aikido: Ruby gems run code without an install script through two paths: native extensions declared in the gemspec (extconf.rb, mkrf_conf.rb, Rakefile), which RubyGems runs at install, and lib/rubygems_plugin.rb, which loads at the end of gem install and again on every later gem command (gem list, gem search, gem -v). Stable RubyGems/Bundler 4.0.x cannot turn either off (--no-build-extension and --no-install-plugin rejected); 4.1.0.beta3 adds opt-out settings, on by default. Tested on Ruby 4.0.9, RubyGems/Bundler 4.0.23 and 4.1.0.beta3. Reported to RubyGems security 27 Oct 2026. Four old research-style gems still installable on 4 Nov send data on install: xmastree 0.0.2/0.0.1 (cryptic-ocean-8847.herokuapp.com/usernames), ancient-city-ruby 2013.1 (stark-samurai-8122.herokuapp.com/logs), hacky 0.0.2 (macOS screenshot to appfolio.herokuapp.com/upload), et_phone_home 0.1.0 (176.31.142.25/ruby_no_way_you_got_here_randomly). strutil-core 1.0.1 used extconf.rb to launch a credential stealer; removed 1 Nov. Fix: gem update --system 4.1.0.beta3; gem install --no-build-extension --no-install-plugin; bundle config set --global no_build_extension true / no_install_plugin true; CI BUNDLE_NO_BUILD_EXTENSION=true BUNDLE_NO_INSTALL_PLUGIN=true; review Gem.plugindir; inspect with gem fetch/unpack. Published 5 Nov, updated 9 Nov.","published_at":"2026-11-05T00:00:00+00:00","relation":"describes","confidence":null,"note":null}],"disclosures":[],"exploit_sources":[],"notice":"Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai"}