A few weeks ago, Axios, the popular HTTP client for JavaScript, suffered a supply chain attack on NPM. An attacker compromised the lead maintainer’s NPM account through social engineering and published two backdoored versions that delivered a cross-platform remote access trojan (RAT) to macOS, Windows, and Linux systems. Axios has over 100 million weekly downloads. The blast radius was enormous.
Not long before that, LiteLLM, a popular Python AI gateway, had a similar incident on PyPI. Compromised credentials were used to push malicious packages that harvested environment variables, SSH keys, cloud credentials, and database passwords.
Both attacks followed the same playbook: gain access to a maintainer’s account, then push a new version with malicious code outside of the normal release process. No code review. No CI. Just a direct publish to the package registry.
Ruby is equally vulnerable to this type of attack
RubyGems hasn’t had a major attack like this yet, but we should be proactive in securing the ecosystem before it happens. Nate Berkopec (maintainer of Puma and a longtime voice in the Ruby community) estimates that 75% of the gems on RubyGems are vulnerable to this type of attack.
There’s already been some progress. Since 2022, RubyGems has required MFA for the most popular gems, those with more than 180 million total downloads. In practice, that threshold covers about 370 gems out of over 190,000 on RubyGems.org. The heaviest-hit packages are protected, but the long tail of gems (and the transitive dependencies they pull in) is still wide open.
The fix is straightforward: gems can require multi-factor authentication (MFA) for all pushes by adding a single line to their gemspec:
spec . metadata [ "rubygems_mfa_required" ] = "true"
After releasing a new version with this set, RubyGems.org will reject any gem push from an account that doesn’t have MFA enabled. Even if an attacker compromises a maintainer’s password or API key, they still can’t publish a new version without the second factor. It doesn’t make the gem invulnerable, but it raises the bar significantly.
If you publish your gem from CI, you might be wondering how MFA fits into an automated release workflow. The answer is Trusted Publishing, which lets RubyGems.org authenticate your CI provider via OIDC instead of a long-lived API key. MFA and Trusted Publishing are complementary: MFA protects interactive pushes, and Trusted Publishing removes the need for shareable credentials in CI.
The good thing about open source is that we can all help make it more secure. Here’s what I propose:
Nate wrote an audit script you can run in your project to see which gems in your Gemfile don’t require MFA on push. Run it. The results might surprise you.
Pick one or a few gems that don’t require MFA and open a PR adding the line above to their gemspec. The change is a one-liner and the PR description mostly writes itself. You can link to this post or to the Axios incident and explain why it matters.
If you maintain any gems, add rubygems_mfa_required to your gemspec and make sure all owners on RubyGems.org have MFA enabled on their accounts.
A fellow thoughtbotter opened an issue on the RubyGems roadmap to start planning a path toward requiring MFA for all gems. It floats a few ideas: progressively lowering the download threshold, requiring MFA for all new gems, and more. If you have thoughts on how to get there, or just want to voice support, jump in.
I’ve been opening PRs on thoughtbot gems and other open source projects, and I encourage you to do the same. Maintainers have been receptive, so these PRs tend to get merged quickly. I also got a PR merged on Bundler adding a commented-out rubygems_mfa_required line to the bundle gem template to nudge authors of new gems to enable this.
If we all do our part, we can make the Ruby ecosystem safer for everyone. Let’s get to work!