RubyGems
RubyGems is Ruby's package manager and the gem command that installs gems from RubyGems.org, where installing a gem can execute code.
Also known as RubyGems.org, gem command
RubyGems is the package manager for the Ruby language, distributing libraries called gems through the central registry at RubyGems.org. The gem command resolves, downloads, and installs gems, and because a gem can ship native extensions and plugins, installing one can run arbitrary Ruby on the machine that installs it.
RubyGems hosts 197,923 gems and has served 272,661,922,445 downloads as of October 2026 (rubygems.org/stats). It has shipped inside standard Ruby since Ruby 1.9, so almost every Ruby install can fetch and run code from the registry out of the box. That reach is also the attack surface: the single most important fact about RubyGems security is that gem install is not a file copy. It builds native extensions and loads plugins, and both run code before you have written a line that uses the gem. We think the registry's recent defenses matter, but the install-time execution model is the thing to understand first.
What is RubyGems?
#RubyGems is Ruby's package manager and the gem command-line tool that drives it. It was released on Pi Day, March 14, 2004, by Chad Fowler, Jim Weirich, David Alan Black, Paul Brannan, and Richard Kilmer at RubyConf 2004. A gem is a packaged Ruby library with a name, a version, and a platform, published to and resolved from a registry, by default the public RubyGems.org. Bundler, the dependency resolver most Ruby projects use, reads a Gemfile and writes a Gemfile.lock, then asks RubyGems to install the exact versions it pinned.
Governance of the tools changed in 2025. On October 17, 2025, Ruby's creator Yukihiro "Matz" Matsumoto announced the Ruby core team would assume stewardship of RubyGems and Bundler from Ruby Central, and on October 26 the removed maintainers agreed to transfer all rights, including the Bundler trademark, to Matz, as Socket reported on the RubyGems stewardship transfer. Ruby Central continues to operate the RubyGems.org service while the Ruby core team owns the client code.
How many gems are on RubyGems?
#RubyGems.org lists 197,923 gems, 252,020 users, and 272,661,922,445 total downloads as of October 2026 (rubygems.org/stats). The most downloaded gem is Bundler itself at 3,871,511,795 downloads, followed by aws-sdk-core at 1,778,312,229. The long tail matters for security: most gems have few downloads, and a brand-new version of any gem is installable the moment it is published, which is the window attackers exploit.
What is inside a .gem file?
#A gem is a tar archive containing three gzipped members: metadata.gz, data.tar.gz, and checksums.yaml.gz. We built a minimal gem and listed it to confirm the layout:
$ tar -tf hello-ext-0.1.0.gem
metadata.gz
data.tar.gz
checksums.yaml.gzThe archive has three members. metadata.gz is the YAML gemspec: name, version, dependencies, and the extensions and files lists that name which files RubyGems will execute. data.tar.gz is the actual library tree, including extconf.rb, any rubygems_plugin.rb, and every .rb file; the behavior lives here. checksums.yaml.gz holds SHA-256 and SHA-512 digests of the other two members. Those checksums bind the parts to each other, not to a publisher identity, so a valid, internally consistent gem proves nothing about who built it or what it does. This is why a software supply chain defense that checks only metadata misses the point: you have to analyze what is in data.tar.gz.
Why does gem install run code?
#RubyGems supports native extensions. When a gemspec lists an extensions entry such as ext/foo/extconf.rb, the gem command runs that Ruby file during installation to generate a Makefile and compile C code. The file executes with the privileges of whoever runs gem install. We verified this with a gem whose extconf.rb does no compilation and instead writes a file:
# ext/hello/extconf.rb -- RubyGems runs this during `gem install`
File.write("/tmp/proof", "uid=#{Process.uid} HOME=#{ENV['HOME']}\n")
File.write("Makefile", "all:\n\t@true\ninstall:\n\t@true\nclean:\n\t@true\n")Installing the gem printed Building native extensions. This could take a while... and wrote /tmp/proof with the installing user's UID and home directory. No step asked for consent, and the advertised library was never required. This is the same mechanism the BufferZoneCorp campaign abused: its knot-activesupport-logger gem placed credential-theft logic in extconf.rb so it ran at install time, before the logger was ever called, in Socket's analysis of malicious Ruby gems and Go modules.
The diagram shows the order of execution. gem install runs extconf.rb immediately, and a rubygems_plugin.rb shipped in the gem is copied into the RubyGems plugin directory and loaded on every later gem and bundle command, where it can register callbacks such as Gem.post_install. Both highlighted steps run with the installing user's UID, environment variables, and credentials, before any code the developer wrote requires the library. In our test gem, extconf.rb wrote /tmp/proof with the UID and $HOME, and nothing prompted for consent.
Install-time execution is where most supply chain attacks fire, because it runs on developer laptops and CI runners with access to environment variables, SSH keys, and cloud credentials. We think the historical decision to run install scripts by default was a mistake, and the broader ecosystem now agrees: client-side cooldowns and install-time controls are a direct response.
How have attackers abused RubyGems?
#The abuse is not theoretical, and it is not CVE-shaped. A malicious gem does not get a CVE, so a scanner that only matches known vulnerabilities sees nothing. Three Socket investigations show the range.
In August 2025, Socket's threat research team documented 60 malicious Ruby gems used in a credential-theft campaign running since at least March 2023 under the aliases zon, nowon, kwonsoonje, and soonje. The gems posed as social media automation tools, delivered the advertised function, and silently exfiltrated usernames and passwords to attacker servers, which makes them infostealer malware. They were downloaded over 275,000 times. "This figure does not reflect the number of compromised systems," Socket's researchers wrote, because not every download executes. RubyGems.org's security team removed the active gems after Socket's report, as a follow-up clarification credited.
In May 2026, Socket tracked the GemStuffer campaign, which used RubyGems as a data transport layer rather than a malware channel. Scripts scraped UK local government portals, packed the responses into valid .gem archives, and pushed them to RubyGems.org using hardcoded API keys, 155 package artifacts in all. One variant built the gem with the CLI after fabricating a credentials file under /tmp and overriding HOME; another skipped the CLI entirely and POSTed the raw archive to the registry API. The exfiltration looked like an ordinary release: a single TLS POST to rubygems.org:443 carrying a gzipped tar.
Both campaigns distributed malicious packages through legitimate registry mechanics, and both relied on typosquatting or plausible branding to get installed in the first place. The BufferZoneCorp cluster added a further twist: sleeper gems published with clean, useful code and weaponized in a later version, which is why a one-time review at adoption does not hold.
What controls does RubyGems.org provide, and where do they fall short?
#RubyGems.org has shipped real defenses. Two-factor authentication became mandatory for owners of any gem exceeding 180 million total downloads on August 15, 2022 (The Hacker News), with reminders starting at 165 million. Trusted publishing arrived in December 2023, exchanging a short-lived OIDC token for an API key so CI pipelines never store a long-lived credential. A 2024 maintainer role let owners grant publish rights without handing over account control, following the least-privilege model, per Socket's coverage of the RubyGems maintainer role. The registry also runs push-time validation and checks passwords against Have I Been Pwned.
The newest control is a cooldown. Bundler 4.0.13 added an opt-in cooldown: setting in June 2026 that refuses to resolve a gem version until it has been public for a set number of days, and 4.0.18 extended the --cooldown flag to bundle lock and bundle cache on August 5, 2026, as Socket reported. The filter reads per-version created_at timestamps from RubyGems.org's v2 compact index and skips versions inside the window:
# Gemfile
source "https://rubygems.org", cooldown: 7A careful detail Socket flagged: the exclusion keys on gem name and version and ignores platform, so an attacker cannot push a fresh platform-specific build under an already-aged version number and have it resolve immediately, as Socket's cooldown analysis explains. The registry-independent gem.coop server tested a 48-hour cooldown at the registry layer instead of in the client.
None of these controls is complete. The table below states what each covers and what it does not.
| Control | Stops | Does not stop |
|---|---|---|
| Mandatory 2FA | Credential-stuffing of popular-gem accounts | Session phishing, or a compromised CI token with publish rights |
| Trusted publishing (OIDC) | Theft of a long-lived API key | A compromised maintainer publishing through the legitimate pipeline |
| Cooldown (Bundler / gem.coop) | Auto-installing a malicious version in its first hours | A malicious version that survives the window, and it can delay urgent fixes |
| Push-time validation | Known-bad patterns at upload | Novel malware, and sleeper gems weaponized later |
| Reachability analysis | CVE noise on unreachable code | Malware, which does not depend on a vulnerable call path |
The verdict: layer them. 2FA and trusted publishing harden the publish path, cooldown buys detection time on the install path, and neither tells you what a specific gem actually does. For that you have to analyze the package contents. Socket's reachability analysis for Ruby, in beta since November 2025, computes a function-level call graph to rank which CVEs are actually invoked, because severity score is a poor triage signal. But reachability is a vulnerability-triage tool, and the sharper RubyGems problem is malware that carries no CVE at all.
The open problem is speed. A malicious version is installable the instant it is published, automated installs can consume it in seconds, and the strongest consumer-side defense, the cooldown, is opt-in and off by default. Until delayed resolution or behavioral analysis is the default rather than a flag a developer has to find, the window that the GemStuffer and credential-theft campaigns exploited stays open.
