Ruby's gem ecosystem powers hundreds of thousands of production applications, from Rails monoliths to microservices. RubyGems.org hosts over 170,000 gems, and the average Rails application pulls in well over 100 dependencies. The ecosystem has a strong track record, but supply chain attacks have hit Ruby too. Here is how to protect your applications.
The Ruby Supply Chain Risk Profile
Ruby shares the common supply chain risks with other interpreted language ecosystems:
- Gem extensions. Native extensions compile C code during
gem install. A malicious extension can execute arbitrary code. - Post-install hooks. While less common than npm postinstall scripts, gems can define installation hooks.
- Typosquatting. In 2020, researchers found malicious gems like
atlas-clienton RubyGems.org that exfiltrated credentials. - Account takeover. Compromised maintainer accounts can push malicious gem versions.
- Dependency confusion. Organizations using private gem servers alongside RubyGems.org face the same confusion risks as other ecosystems.
RubyGems.org has responded well. They now require MFA for privileged operations, support WebAuthn, and have a security team that actively removes malicious gems. But you should not rely on the registry alone.
Gemfile.lock: Your First Defense
Gemfile.lock records exact versions of every gem in your dependency tree. It is the most important security file in your Ruby project.
GEM
remote: https://rubygems.org/
specs:
actioncable (7.1.2)
actionpack (= 7.1.2)
activesupport (= 7.1.2)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
Rules:
- Always commit
Gemfile.lock. For applications, this is mandatory. For libraries, it is still useful for reproducible CI. - Use
bundle install --frozenin CI. This fails ifGemfile.lockis out of sync with theGemfile. - Review
Gemfile.lockchanges in PRs. Every version change is a potential risk. - Never run
bundle updatewithout reviewing the diff.bundle updatecan change dozens of dependencies at once.
bundler-audit
bundler-audit checks your Gemfile.lock against the Ruby Advisory Database:
gem install bundler-audit
bundle-audit check --update
The --update flag pulls the latest advisory database before scanning. Run this in CI:
bundle-audit check --update || exit 1
For JSON output suitable for CI integration:
bundle-audit check --format json
Gem Signing
RubyGems supports package signing with X.509 certificates, but adoption is low. You can enforce signature verification for gems that are signed:
gem install some_gem -P MediumSecurity
Security policies:
NoSecurity- No verification (default).LowSecurity- Verify signatures if present.MediumSecurity- Require signatures, verify against trusted certificates.HighSecurity- Require signatures, verify the full certificate chain.
In practice, most gems are not signed, so HighSecurity is not viable for most applications. However, you can configure Bundler to verify signatures for gems that do sign their releases.
Dependency Confusion Defenses
If you host private gems:
- Use Bundler source blocks to restrict where specific gems come from:
# Gemfile
source "https://rubygems.org"
source "https://gems.yourcompany.com" do
gem "yourcompany-auth"
gem "yourcompany-logging"
end
This ensures your internal gems can only be resolved from your private server.
-
Reserve gem names on RubyGems.org. Publish placeholder gems with your internal package names.
-
Use gem name prefixes. Prefix all internal gems with your organization name.
Reviewing New Dependencies
Before adding a gem, evaluate it:
# Check gem info
gem info rails
# Check reverse dependencies (who uses this gem?)
gem dependency rails --reverse-dependencies
# Check the gem's source
bundle open some_gem
Look for:
- Active maintenance and recent releases
- Multiple maintainers
- CI pipeline with tests
- Native extensions (higher risk)
- Post-install hooks
Use bundle viz or bundle list to understand your full dependency tree.
Securing Bundler Configuration
Your .bundle/config and environment variables control Bundler behavior:
# Disable post-install messages (reduces social engineering surface)
bundle config set --local ignore_messages true
# Set deployment mode for production
bundle config set --local deployment true
# Freeze the bundle
bundle config set --local frozen true
In Dockerfiles:
RUN bundle config set --local deployment true && \
bundle config set --local frozen true && \
bundle install --jobs 4 --retry 3
SBOM Generation
Generate a CycloneDX SBOM for your Ruby application:
gem install cyclonedx-ruby
cyclonedx-ruby -p /path/to/project -o sbom.json
This captures your complete gem dependency tree with versions and license information.
How Safeguard.sh Helps
Safeguard.sh monitors your Ruby gem dependencies across every application in your organization. It ingests SBOMs from your CI builds, maps gem versions to known vulnerabilities, and provides a unified dashboard showing your Ruby supply chain risk. When a new advisory appears in the Ruby Advisory Database, Safeguard.sh immediately identifies which of your applications are affected and provides prioritized remediation steps. It gives your security team visibility that individual bundler-audit runs in separate repositories cannot.