If your Ruby application ships a Gemfile.lock, it also ships a list of exact gem versions that attackers can check against public vulnerability databases — and they do this faster than most teams patch. This bundler-audit tutorial walks through scanning a Rails or Sinatra project for gems with known CVEs, reading the results, fixing what's broken, and wiring the check into CI so a vulnerable gem never merges silently again. By the end you'll have bundler-audit running locally and in your pipeline, a repeatable process for triaging findings, and a clear picture of where automated ruby dependency scanning fits alongside the rest of your security tooling. No prior security tooling experience required — just a Ruby project with a Gemfile.lock and about twenty minutes.
Step 1: Install bundler-audit
bundler-audit is a Bundler plugin maintained by the Ruby community that checks your Gemfile.lock against the ruby-advisory-db, a curated database of CVEs and security advisories affecting RubyGems. Install it as a gem:
gem install bundler-audit
If your project pins gem versions through Bundler itself, add it to your Gemfile instead so every contributor gets the same version:
group :development, :test do
gem "bundler-audit", require: false
end
Then run bundle install. Either path gives you the bundler-audit executable, which is the entire surface area of this gem vulnerability check — no daemons, no accounts, no API keys.
Step 2: Update the advisory database
The advisory database is a local git checkout, cached under ~/.local/share/ruby-advisory-db (or ~/.gem/ruby-advisory-db on older versions). It does not update itself, so run this before every audit, and definitely before you trust a "no vulnerabilities found" result:
bundler-audit update
This clones the database on first run and does an incremental git pull afterward, so it's fast after the initial fetch. A stale database is the single most common reason teams miss a CVE that was disclosed last week — treat bundler-audit update as a mandatory first line of any audit script, not an optional nicety.
Step 3: Run your first bundler-audit tutorial scan
From the root of your Ruby project — anywhere Gemfile.lock lives — run:
bundler-audit check
A clean project reports:
No vulnerabilities found
A vulnerable one looks like this:
Name: nokogiri
Version: 1.13.4
Advisory: CVE-2022-29181
Criticality: High
URL: https://github.com/advisories/GHSA-xxxx-xxxx-xxxx
Title: Nokogiri Improperly Handles Certain XML Content
Solution: upgrade to ~> 1.13.6
Vulnerabilities found!
Each finding includes the gem name, the locked version, the CVE or GHSA identifier, a severity rating, and — most usefully — the exact "Solution" line telling you which version resolves it. bundler-audit check exits non-zero when it finds anything, which is what makes it scriptable for CI later.
Step 4: Triage and patch the findings
Not every advisory demands the same urgency. Work through the list with a simple triage pass:
- Criticality and reachability first. A "Critical" advisory in a gem you call directly in request-handling code outranks a "Low" advisory in a gem only used by your Rakefile.
- Check if you're actually affected. Some advisories only apply to specific configurations (e.g., a YAML parser flaw that only triggers with
safe_load: false). Read the advisory URL before assuming exposure. - Bump to the fixed version. Usually this is as simple as:
bundle update nokogiri --conservative
The --conservative flag keeps Bundler from bumping unrelated gems as a side effect, which keeps your diff reviewable. After updating, re-run bundler-audit check to confirm the advisory is gone.
- When there's no fixed version yet,
bundler-auditlets you record a temporary, auditable exception rather than silently ignoring the finding:
# .bundler-audit.yml
ignore:
- CVE-2022-12345 # waiting on upstream fix, tracked in JIRA-4821
Keep this file in version control and revisit it on a schedule — an ignore list that nobody ever prunes is just technical debt with a YAML extension.
Step 5: Add ruby-advisory-db to your dependency update habits
Bundler-audit only tells you about gems already flagged in the advisory database as of your last update. Pair it with routine hygiene:
bundle outdated --strict
This surfaces gems that are behind on releases even before a CVE is filed against the old version — a large share of advisories get published for issues that were already fixed in a newer release months earlier. Running both commands together turns a reactive gem vulnerability check into a proactive ruby dependency scanning habit.
Step 6: Automate the check in CI
Manual runs catch problems today; CI catches problems on every commit. Add a dedicated job so a new advisory — or a newly introduced vulnerable gem — fails the build instead of reaching production.
GitHub Actions example:
name: bundler-audit
on: [push, pull_request]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.3"
bundler-cache: true
- name: Install bundler-audit
run: gem install bundler-audit
- name: Update advisory database
run: bundler-audit update
- name: Run audit
run: bundler-audit check --format json
The --format json flag makes it straightforward to pipe results into a dashboard, a Slack notifier, or a security ticketing system instead of scrolling through raw terminal output. Because bundler-audit check exits with a non-zero status on any finding, no extra scripting is needed to fail the pipeline — the exit code does the work.
Step 7: Extend coverage beyond bundler-audit
bundler-audit is scoped deliberately narrow: it checks locked gem versions against known advisories and flags insecure Gemfile sources (like an http:// gem server). It does not detect vulnerable transitive code paths, license issues, malicious package takeovers, or supply-chain tampering between publish and install. Treat it as one layer of ruby security tooling, not the whole stack — pair it with SAST for your own code and a broader software composition analysis (SCA) tool if you need license and provenance coverage across multiple languages, not just Ruby.
Troubleshooting and verification
"Could not find bundler-audit" after install. Confirm the gem installed into a Ruby version your shell actually uses — rbenv rehash or asdf reshim ruby is often the missing step after a fresh gem install.
bundler-audit update fails with a git error. Corporate proxies and locked-down CI runners sometimes block the GitHub clone. Set HTTPS_PROXY/HTTP_PROXY appropriately, or vendor a mirror of ruby-advisory-db and point bundler-audit at it with the --advisory-db PATH flag.
A finding won't clear after upgrading. Delete Gemfile.lock's cached resolution assumptions by running bundle lock --update GEM_NAME explicitly, then re-run bundler-audit check. Occasionally a transitive dependency, not your direct Gemfile entry, is pinning the old version.
CI passes locally but fails in the pipeline (or vice versa). This almost always means the advisory database is stale in one environment. Confirm the CI job runs bundler-audit update on every run rather than caching the advisory-db directory indefinitely — caching it for more than a day defeats the purpose of the check.
Verify your setup end-to-end by temporarily pinning a gem to a version with a known CVE (check the advisory database's changelog for an example), confirming bundler-audit check fails with the expected identifier, then reverting. This gives you confidence the tool, the database, and your CI wiring are all actually connected — not just present.
How Safeguard Helps
bundler-audit is a solid, focused gem vulnerability check, but most engineering organizations run more than one language, more than one repository, and more than one dependency manifest format. Safeguard extends the same idea — flagging known-vulnerable dependencies before they ship — across your entire software supply chain, not just your Ruby gems.
Safeguard continuously monitors every manifest across every repo (Gemfile.lock, package-lock.json, go.sum, requirements.txt, and more), correlates findings against multiple vulnerability feeds beyond a single advisory database, and prioritizes results by actual reachability in your code rather than raw CVE count — so your team fixes what matters instead of triaging noise. Findings from tools like bundler-audit can feed directly into Safeguard's unified dashboard alongside SAST, secret scanning, and provenance checks, giving security and engineering teams one place to see supply chain risk instead of a different CLI output for every ecosystem. For teams that have outgrown ad hoc CI scripts and ignore-list YAML files, Safeguard turns this same bundler-audit tutorial workflow into a policy that's enforced, tracked, and auditable across the whole organization — not just the repos someone remembered to configure.