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.
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. Closing that gap — matching dependencies against advisories, then layering exploitation signal on top — is what dependency vulnerability scanning is designed to do.
What We'll Cover
- The Ruby/Rails security ecosystem (ruby-advisory-db, OSV, NVD)
- What files GeekWala scans for Ruby projects
- bundler-audit vs GeekWala: comparison table
- Where brakeman fits (and where it doesn't)
- Ruby-specific vulnerability patterns
- Real-world Ruby/Rails vulnerability examples
- Step-by-step scanning workflow
- The Rails blast radius factor
- Best practices for Ruby dependency security
- Frequently asked questions
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 (required for Ruby)
✗ Gemfile Not supported (version constraints only — scan Gemfile.lock)
✗ .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 does not accept Gemfile, so generate the lock file and scan that.
Special cases:
- Multi-gem repos (engines, gems within Rails apps): Upload the root
Gemfile.lock - Platform-specific gems (e.g.,
nokogirion 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.
| Capability | bundler-audit | GeekWala |
|---|---|---|
| Advisory sources | ruby-advisory-db only | OSV (GitHub Security Advisories + ecosystem databases) + EPSS + CISA KEV |
| Insecure source warnings | ✓ (flags HTTP gem sources) | ✗ |
| Exploitation signals | ✗ (severity only) | EPSS + CISA KEV |
| C extension vuln coverage | Limited (ruby-advisory-db only) | Limited too (C-library CVEs appear when a gem's own advisory lists them, as nokogiri's do) |
| Patch version suggestions | ✓ (shows patched versions) | ✓ (with EPSS context on urgency) |
| Historical trend tracking | ✗ | ✓ (per-project vulnerability history; EPSS shown is today's) |
| 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 (via OSV)
│ │ + EPSS + CISA KEV
──────────────────────────────────────────────────────────
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," with no signal on 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 checks each gem against OSV (which includes GitHub's RubyGems advisories) and adds EPSS and CISA KEV to every finding.
Step 3: Interpret results with Rails-specific context
GeekWala enriches each finding with EPSS (exploitation probability), CVSS (severity), and CISA KEV (confirmed active exploitation), and ranks them KEV first, then EPSS above 10%, then severity — work down from the top rather than by CVSS alone.
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: |
jq -n --arg file_name Gemfile.lock --rawfile content Gemfile.lock \
'{file_name: $file_name, content: $content}' \
| curl -sf -X POST https://www.geekwala.com/api/v1/vulnerability-scan/run \
-H "Authorization: Bearer ${{ secrets.GEEKWALA_TOKEN }}" \
-H "Content-Type: application/json" \
-d @-
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 adds the EPSS and KEV exploitation signals that bundler-audit lacks.
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 refreshes EPSS daily and ranks findings by today's score, so a scheduled re-scan brings a jump to the top; in CI, the GitHub Action's epss-threshold input fails the build when a finding crosses the line you set. A jump is 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 on every finding, OSV's advisory coverage, and per-project vulnerability history 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?
Start with the CISA KEV entries (GeekWala ranks them first) — those are confirmed under active exploitation. Then work down the EPSS-ranked list, highest first, 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?
No — GeekWala accepts Gemfile.lock, not Gemfile. Gemfile declares constraints like gem 'rails', '~> 7.1' rather than exact resolved versions, and GeekWala does not resolve dependency trees itself. Run bundle install to generate Gemfile.lock and scan that — 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.


