RubyGems security today is anchored by three controls that actually work — trusted publishing via OIDC, mandatory MFA for high-impact maintainers, and Bundler lockfile checksums — while the control everyone asks about first, gem signing, has been functionally dead for a decade. Knowing which mechanisms carry real weight matters, because Ruby's attack history is almost entirely account takeover: rest-client 1.6.13 in 2019, strong_password 0.0.7 the same year, and a string of similar incidents that all followed the pattern of a dormant maintainer account plus a reused password.
Gem signing: why nobody uses it
RubyGems has shipped X.509 signing since forever:
gem cert --build you@example.com
gem build mygem.gemspec # with signing_key and cert_chain in the gemspec
gem install mygem -P HighSecurity
The problem is the trust model, or rather the absence of one. There's no CA, no key directory, no revocation story — verifying a signature means manually collecting each author's self-signed cert out-of-band and adding it with gem cert --add. Under HighSecurity policy, every gem in the install must be signed, and since only a small fraction of gems ship signatures, the policy is unusable against any real Gemfile. Signing tells you the artifact wasn't modified in transit, which HTTPS to rubygems.org already mostly covers, and nothing about whether the signer is who you think.
Conclusion: don't build policy on gem signatures in 2026. Sigstore-style keyless signing has been discussed in the community for years; until something ships and reaches critical mass, your integrity story lives in the lockfile.
Yanking: removal, not remediation
gem yank rest-client -v 1.6.13 pulls a version from the index — new resolutions won't pick it, installs of that exact version fail, and the version number is permanently burned (you can't republish over it, which prevents silent substitution). That's the good part.
The gaps: yanking doesn't claw back copies already installed, cached in CI images, vendored, or mirrored into corporate gem servers. During the 2019 rest-client incident, the malicious 1.6.13 was downloaded roughly a thousand times before removal; every one of those environments needed detection and cleanup that yanking couldn't provide. Treat a security yank the way you'd treat a recall notice: the shelf is clean, your pantry isn't. bundle exec bundler-audit check --update knows about yanked-for-security versions via ruby-advisory-db and will flag them in your Gemfile.lock even after they've vanished from the index.
Trusted publishing: kill your long-lived API keys
This is the biggest structural improvement in the ecosystem's history. Since late 2023, rubygems.org supports OIDC trusted publishing: you register a specific GitHub repository and workflow as the authorized publisher for a gem, and the release job exchanges a short-lived OIDC token for push access. No GEM_HOST_API_KEY in secrets, nothing to leak, nothing for a departed maintainer to walk away with.
The setup is one config screen on rubygems.org plus a workflow using the official action:
jobs:
release:
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write
contents: write
steps:
- uses: actions/checkout@v4
- uses: rubygems/release-gem@v1
If you publish internal or open source gems and still push with a static API key, migrating is an afternoon and removes your single most-stolen credential class. Pair it with the rubygems_mfa_required: true gemspec metadata flag, which forces MFA for any human pushing that gem — rubygems.org already mandates MFA for maintainers of the top-100 gems, but your gems aren't exempt from the same attack just because they're smaller.
The consuming side: lockfile checksums and bundler-audit
Bundler 2.6 added a CHECKSUMS section to Gemfile.lock:
bundle lock --add-checksums
With checksums recorded, any future install verifies the SHA-256 of every downloaded gem against the lockfile — a compromised mirror, a tampered corporate proxy, or a substituted artifact fails loudly. Combined with frozen mode in CI:
bundle config set --local frozen true
bundle install
bundle exec bundler-audit check --update
you get: no lockfile drift (frozen), no artifact substitution (checksums), and no known-vulnerable or yanked versions (bundler-audit against ruby-advisory-db). That trio is the minimum viable Ruby pipeline, and it costs three lines of CI config.
What it still doesn't cover is the day-zero malicious release — the window between a hijacked gem going live and an advisory existing. Registry-monitoring SCA closes that gap; Safeguard's dependency scanning watches publish events and flags anomalous releases (new maintainer, changed source repo, install-time hooks appearing) rather than waiting for the advisory. Whatever tool you use, the coverage question to ask is "what happens in the first 24 hours," because ruby-advisory-db is volunteer-driven and averages days, not minutes.
Ranking the controls
| Control | Effort | Real-world value |
|---|---|---|
| Frozen bundle + lockfile checksums | Minutes | High — blocks drift and substitution |
| bundler-audit in CI | Minutes | High — known CVEs and yanked versions |
| Trusted publishing (if you publish) | An afternoon | High — eliminates API key theft |
rubygems_mfa_required metadata | Minutes | Medium-high |
| SCA with registry monitoring | Days | Medium-high — closes the day-zero window |
| X.509 gem signing | Weeks of pain | Near zero |
The table is deliberately blunt. Ruby teams keep spending their security budget arguing about signing while shipping unfrozen bundles from workflows authenticated with a five-year-old API key stored in four places.
Frequently asked questions
Should I require signed gems with HighSecurity policy?
No — the signing ecosystem never reached the adoption needed to make the policy usable, since HighSecurity requires every gem in the dependency graph to be signed. Base integrity on Bundler's lockfile checksums and frozen mode instead.
Does yanking a malicious gem version end the incident?
It stops new installs, nothing more. Copies persist in CI caches, Docker layers, vendored directories, and mirrors, so you still need to sweep environments for the bad version — bundler-audit will flag it in lockfiles even after it's gone from the index.
Is trusted publishing worth it for a small internal gem?
Yes, arguably more so — small gems are maintained by one or two people whose long-lived API keys never get rotated. Trusted publishing replaces that standing credential with a per-run OIDC token scoped to one repo and workflow.
How fast do Ruby advisories appear after an incident?
ruby-advisory-db is community-maintained and typically lags disclosure by days. For the window before an advisory exists, you need registry-level anomaly monitoring from an SCA platform, or you're relying on someone else tweeting about it first.