Poisoned Pipeline / Incidents / AS347

AS347 · 2026-11

Four gems from 2013-2015 still run code at install through extension build scripts, and a plugin file would run on every gem command

RubyGemsDeveloper workstation

Aikido published this on 5 November 2026. On 26 October it pulled the
top thousand gems by total downloads from bestgems.org and read their
published gemspecs: 74 of 997 declare extension build steps, including
bcrypt, bigdecimal, bootsnap, bson and debug, and 2 of 997 ship a
RubyGems plugin file, rdoc and yard. The count is not a risk measure,
because an attacker can add either at any version. It measures how
ordinary the two mechanisms look to a reviewer.

Neither mechanism is an install script. A gem declares extensions in
its gemspec, and if one of them is an extconf.rb build script RubyGems
runs that Ruby file during installation and then runs the Makefile it
generated, the way node-gyp builds native npm packages. RubyGems runs
mkrf_conf.rb the same way and then runs rake in the same directory, and
it supports configure, Rakefile, CMake and Cargo builds, each running
code through its own tool. The attacker adds one file and one line in
the gemspec, and there is no separate scripts section for a reviewer
to notice. Separately, a gem that ships lib/rubygems_plugin.rb is
installed as a RubyGems plugin: RubyGems loads it at the end of gem
install and on every later gem command, including read-only ones such
as gem list, gem search and gem -v, and reports plugin failures as
warnings so the command continues. Bundler installs into vendor/bundle
do not load plugins on a plain gem list, but bundle exec gem list does.

While testing its detection rules Aikido found four gems, published
between 2013 and 2015, that run code at install and are still
installable. xmastree 0.0.2 lists a Rakefile under extensions and POSTs
the output of whoami to cryptic-ocean-8847.herokuapp.com/usernames;
xmastree 0.0.1 carries the same payload and URL but an empty extensions
list, so it runs only if someone runs rake inside the gem directory.
ancient-city-ruby 2013.1 POSTs whoami to
stark-samurai-8122.herokuapp.com/logs and then deletes itself. hacky
0.0.2 takes a silent screenshot on macOS, uploads it to
appfolio.herokuapp.com/upload, then deletes the screenshot and itself.
et_phone_home 0.1.0 sends a GET request to
176.31.142.25/ruby_no_way_you_got_here_randomly at install. Aikido
reads them as proof-of-concept or research tests rather than a
campaign, and says that they still run on install and still send data
off the machine.

Disclosure has not removed them. Aikido reported the extension and
plugin execution paths to the RubyGems security team on 27 October;
the team asked about the opt-out flags and has not commented further.
It reported the four gems on 30 October. On 4 November all four were
still available on RubyGems.

There is no off switch on stable RubyGems. The --no-build-extension
and --no-install-plugin flags exist only in 4.1 prereleases, and even
in 4.1.0.beta3 both behaviours stay on unless the flags are passed;
stable 4.0.x rejects them as an invalid option. The equivalent
Bundler settings are no_build_extension and no_install_plugin, also
4.1.0.beta3 only. Turning them off breaks loading for gems whose
extensions were skipped: RubyGems prints "Ignoring <gem> because its
extensions are not built", Bundler reports "Could not find <gem>" so
bundle exec fails, and bundle pristine fails the same way in 4.1.0.beta3
though it works for a gem that had only its plugin skipped. Precompiled
platform builds declare no extensions, so they keep loading. Aikido
tested on Ruby 4.0.9 with RubyGems and Bundler 4.0.23 and 4.1.0.beta3.

The pattern is the one npm has already been through. Miasma moved its
execution trigger out of package.json into binding.gyp, which node-gyp
runs automatically, and npm 12 answered by making dependency install
scripts opt-in. Aikido also cites SleeperGem — mapped here as AS68 —
as evidence that attackers already work RubyGems, publishing malicious
versions from what appear to be compromised dormant maintainer accounts.

Mapped techniques

supply-chain Four gems on RubyGems

T0109 · Resource Development · Publish malicious artifact

Four gems published to RubyGems between 2013 and 2015 under
their own names, still installable on 4 November 2026. No
compromised maintainer account is involved; these are the
publisher's own packages.

T0151 · Execution · Installation scripts

Each gem lists a Rakefile or mkrf_conf-style build under
extensions in its gemspec. RubyGems runs that file as Ruby
during gem install, then runs the generated Makefile or rake,
so there is no scripts section and no install hook for a
reviewer to inspect.

T0159 · Execution · Malicious artifact execution

Installing the gem was enough for the payload to run. xmastree
0.0.1 shows the trigger precisely: same file, same payload,
empty extensions list, so it does nothing at install and only
runs if someone runs rake inside the gem directory.

post-supply-chain Machines that install the gems

T0156 · Exfiltration · Exfiltration over webhooks

xmastree and ancient-city-ruby POST whoami output to Heroku
endpoints, hacky uploads a silent macOS screenshot to another,
and et_phone_home sends a GET beacon to a bare IP. Two of the
payloads delete themselves after sending.

Framework gaps noted

Sources