Loading...
Skip to main content
Ecosystem Guide

Ruby Gem Security: Beyond bundler-audit for Production Rails Apps

bundler-audit checks ruby-advisory-db and stops there. It won't tell you which gem vulnerabilities are actually being exploited. Learn how EPSS and CISA KEV signals change Ruby dependency prioritization for production Rails apps.

Sudhir P.
Last updated
12 min read

A Rails team patched Devise after a disclosure, then moved on. Three weeks later, an attacker chained a transitive vulnerability in ActiveSupport's XML serializer — a CVE rated "medium" that nobody prioritized — into a full remote code execution. bundler-audit had flagged it. Nobody triaged it because there was no signal that anyone was actually exploiting it.

That's the gap. Ruby's security tooling tells you what's vulnerable. It doesn't tell you what's being exploited right now.

Key Takeaway

TL;DR: bundler-audit is essential for local development — it checks the curated ruby-advisory-db and catches known gem vulnerabilities fast. But it only reports severity, not exploitation activity. For production Rails apps with 150+ gems, you need EPSS (exploitation probability) and CISA KEV to separate the 3 urgent findings from the 40 that can wait. Always scan Gemfile.lock, not Gemfile — without pinned versions, scanning is guesswork.

What We'll Cover

The Ruby/Rails Security Ecosystem

Ruby's advisory landscape is mature but fragmented. The community maintains high-quality databases, but no single source covers everything:

ruby-advisory-db       Community-maintained (rubysec.com)
  ├─ Ruby/Rails-specific advisories
  ├─ Curated by RubySec team
  └─ ~900 advisories (most comprehensive for gems)

OSV (Open Source Vulnerabilities)
  ├─ Multi-ecosystem format
  ├─ Includes RubyGems advisories
  └─ Cross-indexes with npm, Python, Java, Go

NVD (National Vulnerability Database)
  ├─ Some Ruby/Rails CVEs
  ├─ Stronger on C extension vulns (nokogiri, grpc)
  └─ Secondary source (incomplete for pure-Ruby gems)

GitHub Security Advisory
  ├─ Auto-detected from Gemfile.lock
  └─ Sometimes leads ruby-advisory-db on disclosures

ruby-advisory-db is the gold standard for gem vulnerabilities. It's what bundler-audit uses. But "most comprehensive" doesn't mean "everything" — C extension vulnerabilities in gems like nokogiri or grpc often appear in NVD before ruby-advisory-db picks them up. Some advisories show up in OSV or GitHub first.

The real gap: ruby-advisory-db has advisory data. It doesn't have EPSS scores (exploitation probability) or CISA KEV entries (actively exploited). You get "this gem has a vulnerability" but not "is anyone actually exploiting it?"

What Files GeekWala Scans for Ruby

GeekWala scans Ruby projects using:

✓ Gemfile.lock          Exact versions, full dependency tree (strongly recommended)
✓ Gemfile               Manifest with version constraints (partial — resolution needed)
✗ .gemspec              Not supported (library metadata, abstract versions)
✗ vendor/cache/*.gem    Not supported (binary format)

Always scan Gemfile.lock. It's the source of truth — exact versions for every gem in your dependency tree, including transitive dependencies. Gemfile declares constraints like gem 'rails', '~> 7.1', which can't be mapped to a specific vulnerable version without resolution. GeekWala parses Gemfile as a fallback but results are more accurate with the lock file.

Special cases:

  • Multi-gem repos (engines, gems within Rails apps): Upload the root Gemfile.lock
  • Platform-specific gems (e.g., nokogiri on Linux vs macOS): GeekWala checks all platform variants
  • Git-sourced gems: Tracked by version tag when available; best-effort for untagged commits

bundler-audit vs GeekWala: When to Use Which

bundler-audit is the standard Ruby vulnerability scanner. It's fast, runs locally, and integrates into CI pipelines. But it has a single data source and no exploitation context.

bundler-audit's strengths are Ruby-native: it reads Gemfile.lock directly, integrates with bundle exec, and the ruby-advisory-db is curated by Ruby community members who understand gem-specific risks. It also flags insecure gem sources (non-HTTPS) — something vulnerability scanners don't check.

Capabilitybundler-auditGeekWala
Advisory sourcesruby-advisory-db onlyruby-advisory-db + OSV + NVD + CISA KEV
Insecure source warnings✓ (flags HTTP gem sources)
Exploitation signals✗ (severity only)EPSS + CISA KEV
C extension vuln coverageLimited (ruby-advisory-db only)Cross-database (catches nokogiri/grpc vulns in NVD)
Patch version suggestions✓ (shows patched versions)✓ (with EPSS context on urgency)
Transitive path tracing✗ (lists affected gem only)✓ (shows full chain: app → rails → actionpack → CVE)
Historical trend tracking✓ (track EPSS evolution over time)
CI integration✓ native (bundle exec bundler-audit)✓ (GitHub Actions, webhooks)

Use bundler-audit for local development — run it in CI alongside your test suite. Its insecure-source detection catches configuration risks that vulnerability scanners miss.

Use GeekWala for production monitoring — when you need to know not just "does this gem have a vulnerability?" but "is anyone actually exploiting it?" EPSS and KEV answer that question. Scan your Ruby dependencies →

Running both in CI is the practical setup: bundler-audit as a fast, native pre-merge check, GeekWala for the exploitation-aware view once code ships. See dependency scanning in GitHub Actions for wiring either (or both) into your pipeline.

Where brakeman Fits (and Where It Doesn't)

Ruby teams often ask whether brakeman replaces bundler-audit or GeekWala. It doesn't — it solves a different problem entirely.

brakeman is a static analysis security scanner for Rails applications. It reads your app's own code — controllers, views, models — and flags patterns like SQL injection via string interpolation, unsafe eval, mass assignment, and cross-site scripting in ERB templates. It has zero visibility into your gems' vulnerability history, because that's not what it's built to check.

Scope comparison
──────────────────────────────────────────────────────────
Tool          │ What it scans         │ What it finds
──────────────┼────────────────────────┼─────────────────────
brakeman      │ Your app's own code    │ SQLi, XSS, mass
              │ (controllers, views,   │ assignment, unsafe
              │ models)                │ redirects, eval
──────────────┼────────────────────────┼─────────────────────
bundler-audit │ Gemfile.lock           │ Known CVEs in gems
              │                        │ (ruby-advisory-db)
──────────────┼────────────────────────┼─────────────────────
GeekWala      │ Gemfile.lock           │ Known CVEs + EPSS +
              │                        │ CISA KEV across 4
              │                        │ databases
──────────────────────────────────────────────────────────

A vulnerable gem and vulnerable app code are two separate risk categories, and Rails teams need coverage for both. brakeman won't tell you your pinned nokogiri version has a memory-corruption CVE — that's bundler-audit's or GeekWala's job. Conversely, bundler-audit won't tell you your UsersController builds a SQL query with unsanitized string interpolation — that's brakeman's job.

Run all three: brakeman in CI to catch code-level issues before merge, bundler-audit for a fast native dependency check, and GeekWala for the exploitation-aware view (EPSS + KEV) neither of the other two provides. None of them substitutes for the others.


Stop guessing which gem vulnerabilities actually matter.

Upload your Gemfile.lock → — get EPSS exploitation scores and CISA KEV status on every finding in under a minute. No account needed.


Ruby-Specific Vulnerability Patterns

Rails' convention-over-configuration philosophy creates vulnerability categories that don't exist in other ecosystems:

Ruby/Rails Vulnerability Category Matrix
─────────────────────────────────────────────────────────────────────
Category          │ Detected by     │ Priority    │ Example Gems
                  │ bundler-audit?  │ Multiplier  │
──────────────────┼─────────────────┼─────────────┼──────────────────
Deserialization   │ Yes             │ HIGH        │ rails, oj, psych
  (YAML/Marshal)  │                 │ (code exec) │ syck, ox
──────────────────┼─────────────────┼─────────────┼──────────────────
SQL injection     │ Yes             │ HIGH        │ activerecord
  (ActiveRecord)  │                 │ (data leak) │ sequel, rom-sql
──────────────────┼─────────────────┼─────────────┼──────────────────
C extension bugs  │ Partial         │ HIGH        │ nokogiri, grpc
  (native gems)   │ (NVD needed)    │ (memory)    │ ffi, pg, mysql2
──────────────────┼─────────────────┼─────────────┼──────────────────
Auth/session      │ Yes             │ HIGH        │ devise, omniauth
  bypass          │                 │ (access)    │ warden, doorkeeper
──────────────────┼─────────────────┼─────────────┼──────────────────
XSS in views      │ Yes             │ MEDIUM      │ actionview, haml
  (template       │                 │             │ slim, erubi
  engines)        │                 │             │
──────────────────┼─────────────────┼─────────────┼──────────────────
Transitive chains │ Partially       │ CONTEXT     │ rails → actionpack
  (framework      │ (direct gem     │ -DEPENDENT  │ → rack → CVE
  dependencies)   │ only)           │             │
──────────────────┼─────────────────┼─────────────┼──────────────────
Dev gems in prod  │ No              │ MEDIUM      │ pry, byebug
  (group leakage) │                 │             │ better_errors
──────────────────┼─────────────────┼─────────────┼──────────────────
Yanked gems       │ Yes (with       │ HIGH        │ Any gem removed
  (supply chain)  │ --update flag)  │ (trust)     │ from rubygems.org
─────────────────────────────────────────────────────────────────────

The key insight: Rails apps have deep transitive chains. gem 'rails' pulls in actionpack, actionview, activesupport, rack, and dozens more. A vulnerability in rack affects every Rails app — but bundler-audit only shows "rack has a CVE," not which of your gems brought rack in or whether anyone is exploiting it.

Real-World Ruby/Rails Vulnerability Examples

Ruby vulnerabilities rarely stay contained to the gem that's flagged. Rails' deep transitive tree means a single CVE can affect dozens of unrelated-looking apps.

Example 1: rack → actionpack → your app

rack sits underneath every Rails and Sinatra app, and it's pulled in transitively — most Ruby developers never add it to their Gemfile directly:

your-rails-app/
├── Gemfile.lock
│   └── rails (7.1.3)              ← You installed this
│       └── actionpack (7.1.3)     ← Came along for the ride
│           └── rack (3.0.8)       ← Two levels down
│               └── 🔴 rack CVE (request smuggling class)
└── ...

bundler-audit flags "rack has a CVE." GeekWala identifies the exact affected package and version — rack-3.0.8 — plus its EPSS and CISA KEV context, so you're prioritizing based on exploitation signal instead of guessing. Either way, the fix is bumping rails (which pulls a patched rack), not pinning rack directly — direct pinning can break actionpack's version constraints.

Example 2: nokogiri and the libxml2 problem

nokogiri wraps libxml2 and libxslt — C libraries with a long history of memory-corruption CVEs. When a libxml2 CVE lands, every app with nokogiri in its dependency tree is affected, whether it's used directly for XML/HTML parsing or pulled in transitively by another gem, including capybara in test suites. CVSS on these routinely scores 8+, but EPSS separates the findings under active exploitation from the ones that stay theoretical.

Example 3: YAML deserialization via Psych

Ruby's YAML parser (Psych) deserializes arbitrary Ruby objects by default in older configurations — a remote code execution vector if you load untrusted YAML. It's the same deserialization class that has surfaced in Python's PyYAML and in PHP's unserialize() gadget chains (see our Packagist guide). CVSS says "Critical." EPSS is often much lower, because most exploit paths require the attacker to control input that reaches YAML.load directly instead of the safer YAML.safe_load. GeekWala shows both scores so you decide instead of guessing.

Scanning Ruby Dependencies: A Practical Workflow

Step 1: Prepare your Gemfile.lock

# Update dependencies
bundle update

# Or just ensure lock file is current
bundle install

Always commit Gemfile.lock. Without it, bundle install resolves versions differently across environments. Your dev machine runs rack 3.0.8, CI runs 3.0.9, production runs 3.0.7. Scanning becomes meaningless.

Step 2: Upload to GeekWala

Visit GeekWala's Ruby scanning page and upload Gemfile.lock. GeekWala queries ruby-advisory-db, OSV, NVD, and CISA KEV simultaneously.

Step 3: Interpret results with Rails-specific context

GeekWala enriches each finding with EPSS (exploitation probability), CVSS (severity), and CISA KEV (confirmed active exploitation). Sort by KEV first, then EPSS — not by CVSS.

For Rails apps, pay extra attention to these high-impact gems:

Critical Ruby packages (vulnerability here = urgent regardless of EPSS):
  rails/actionpack   → Request handling, every Rails app
  nokogiri           → XML/HTML parsing, C extension attack surface
  devise/omniauth    → Authentication = direct exposure
  rack               → HTTP layer, under every Rails/Sinatra app
  puma/unicorn       → App server = network-facing

Step 4: Set up continuous monitoring

Add GeekWala to your CI pipeline:

# .github/workflows/security.yml
name: Dependency Security Scan
on:
  push:
    paths: ['Gemfile.lock']
  schedule:
    - cron: '0 8 * * 1'  # Weekly Monday 8am

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: bundler-audit (local)
        run: |
          gem install bundler-audit
          bundle-audit check --update
      - name: GeekWala scan (enriched)
        run: |
          curl -s -F "file=@Gemfile.lock" \
            -F "ecosystem=rubygems" \
            https://www.geekwala.com/api/v1/scan \
            -H "Authorization: Bearer ${{ secrets.GEEKWALA_TOKEN }}"

Step 5: Triage with EPSS context

When bundler-audit and GeekWala both flag findings, use EPSS to prioritize. A rack vulnerability with EPSS 0.7 and a KEV entry needs an emergency patch. A debug-gem advisory with EPSS 0.02 can wait for your next dependency update cycle.

The Rails Blast Radius Factor

A typical Rails app has 150-250 gems in Gemfile.lock. Most come from a small number of foundational dependencies:

Gem Adoption in Rails Projects (approximate)
──────────────────────────────────────────────
rack         ████████████████████████████  ~95%
actionpack   ████████████████████████████  ~95%
nokogiri     ██████████████████████████    ~85%
devise       ████████████████████████      ~70%
puma         ████████████████████████      ~70%
sidekiq      ██████████████████████        ~60%
pg           █████████████████████         ~55%
redis        █████████████████████         ~55%
turbo-rails  █████████████████             ~45%
──────────────────────────────────────────────

This concentration means a vulnerability in rack or nokogiri has an enormous blast radius — it affects nearly every Rails app. When you see a high EPSS score on one of these gems, treat it as urgent: attackers know the same adoption numbers you do, and they target widely-used gems for maximum impact.

Conversely, a vulnerability in a niche gem with EPSS 0.02 and no KEV entry is genuinely low priority. This is where EPSS saves you from treating all CVSS 7.0+ findings equally.

Best Practices for Ruby Dependency Security

1. Commit Gemfile.lock, always

Your Gemfile.lock is the source of truth for every gem version in your dependency tree. Without it, bundle install resolves differently per environment. Scanning without a lock file is scanning a guess. Commit it, review changes to it in PRs, and treat lock file diffs as security-relevant.

2. Run bundler-audit in CI, GeekWala for monitoring

bundler-audit is fast and catches known advisories with zero setup. Run it on every push. Use GeekWala for weekly scheduled scans with EPSS enrichment — it catches the C extension vulns and exploitation signals that bundler-audit misses.

3. Audit your Gemfile groups

Dev and test gems that leak into production are a real risk. better_errors exposes a full REPL in the browser. pry-remote opens a network socket. Verify your Gemfile groups are correct and your deployment excludes dev/test:

bundle install --without development test

4. Pin nokogiri and update it aggressively

nokogiri wraps libxml2 and libxslt — C libraries with a long history of memory corruption vulnerabilities. When nokogiri releases a security update, apply it within days, not weeks. Its C extension attack surface makes every vulnerability higher-risk than the CVSS score alone suggests.

5. Track EPSS trends, not just scores

A gem vulnerability might open at EPSS 0.05, spike to 0.7 when a PoC drops, then stabilize at 0.4. GeekWala tracks this evolution. Set up alerts for EPSS spikes — that's your signal that threat actors are interested and you need to move faster.

6. Run brakeman alongside your dependency scanner

Dependency scanning and static app-code analysis cover different risk categories. brakeman catches SQL injection, mass assignment, and unsafe redirects in your own controllers and views — none of which bundler-audit or GeekWala can see, because neither reads your application code. Run both in CI.

Frequently Asked Questions

Does GeekWala replace bundler-audit?

No — run both. bundler-audit is fast, runs offline, and belongs in every CI pipeline as a pre-merge gate. GeekWala adds EPSS and CISA KEV context, cross-database coverage (OSV + NVD alongside ruby-advisory-db), and historical trend tracking for production monitoring. They solve overlapping but different problems: local speed versus exploitation-aware prioritization.

My Gemfile.lock has 200+ gems — where do I start?

Filter for CISA KEV entries first — those are confirmed under active exploitation. Then sort the rest by EPSS descending, not by CVSS. A CVSS 9.8 finding with EPSS 0.01 is far less urgent than a CVSS 6.5 finding with EPSS 0.85 and a KEV entry. See our EPSS deep dive for the full prioritization framework.

Can GeekWala scan a Gemfile without a lock file?

Yes, as a fallback, but results are less accurate. Gemfile declares constraints like gem 'rails', '~> 7.1' rather than exact resolved versions, so GeekWala has to guess at resolution the same way any scanner would. Run bundle install to generate Gemfile.lock and scan that instead — it's the source of truth for what's actually installed.

What about gems installed from git sources instead of RubyGems.org?

GeekWala tracks git-sourced gems by version tag when one is present in Gemfile.lock. Untracked or untagged git commits are best-effort — there's no canonical version to check against an advisory database, so treat these gems as an audit gap rather than a confirmed-clean result.

Does GeekWala catch vulnerabilities in Rails engines or mountable gems?

Yes, as long as the engine's dependencies resolve into your root Gemfile.lock. Multi-gem repositories — a Rails app with internal engines — should upload the root lock file, since that's what contains the fully resolved dependency tree, including anything the engines pull in.

Is a vulnerable dev-only gem (like pry or byebug) actually a risk?

Only if it leaks into production. Gemfile groups (group :development, :test do ... end) should keep these out of your deployed bundle. Verify with bundle install --without development test during deploy. If a dev gem shows up in a production scan, that's a signal your groups are misconfigured, not just a vulnerability to patch.

See Dependency Security Across 8 Ecosystems for how Ruby fits into multi-language stacks. For a side-by-side comparison of all ecosystem scanners, see 7 Dependency Vulnerability Scanners Compared.

2026 Supply Chain Alert: RubyGems.org has faced repeated waves of typosquatting and account-takeover attacks targeting popular gems. If your Ruby projects pull from RubyGems.org, read Supply Chain Attacks on Open Source in 2026 for the latest attack patterns and defenses.


bundler-audit tells you what's vulnerable. GeekWala tells you what's being exploited.

Upload your Gemfile.lock and see what's actually urgent → — every vulnerability ranked by EPSS exploitation probability and CISA KEV status. Know in 60 seconds which Ruby findings need an emergency patch. No account needed.