Skip to content
ExponaBLOG
RUBY ON RAILS · DAY 046 / 100

Brakeman and bundler-audit: patterns, pitfalls and fixes

Automated security checks.

Expona Admin5 min read

Security scanning is non-negotiable in production Rails

Rails applications handle user data, payment information, and sensitive business logic—yet many teams ship code without running automated security checks. By Day 46, we've built features, tested them, and deployed them to staging. Now it's time to lock down the most critical gap: catching known vulnerabilities before they reach production. Brakeman and bundler-audit: patterns, pitfalls and fixes form the backbone of any serious Rails security posture, scanning both your custom code and your dependencies for problems you might otherwise miss.

Brakeman scans code for vulnerabilities

Brakeman is a static analysis tool that examines your Rails codebase for common security issues—SQL injection, mass assignment, unsafe redirects, and template injection. It runs without executing your code, making it fast and suitable for CI/CD pipelines.

Install Brakeman in your Gemfile:

ruby
group :development do
  gem 'brakeman', require: false
end

Run it from the command line:

ruby
bundle exec brakeman

This generates a report of findings ranked by confidence level. A typical output includes warnings like this:

ruby
[Notice] Unprotected mass assignment in UsersController:
  User.new(params[:user])

The issue: if a user crafts a request with extra parameters, they might set admin flags or bypass business logic. The fix is to use strong parameters:

ruby
def create
  @user = User.new(user_params)
  @user.save
end

private

def user_params
  params.require(:user).permit(:email, :name)
end

Another common finding is SQL injection. Brakeman flags string interpolation in queries:

ruby
# Brakeman warning: SQL injection risk
User.where("email = '#{params[:email]}'")

# Safe version
User.where(email: params[:email])

Brakeman also catches unsafe redirects, where user input is passed directly to redirect_to:

ruby
# Risk: open redirect vulnerability
redirect_to params[:next_page]

# Safe: whitelist destinations
SAFE_PATHS = ['/dashboard', '/profile']
redirect_to params[:next_page] if SAFE_PATHS.include?(params[:next_page])

Run Brakeman on every pull request. It takes seconds and often catches problems before code review.

bundler-audit checks gems for known vulnerabilities

Your Rails app depends on dozens of gems, many of which are maintained by volunteers. When vulnerabilities are discovered in these dependencies, bundler-audit alerts you to outdated versions.

Install bundler-audit:

ruby
group :development do
  gem 'bundler-audit', require: false
end

Run it:

ruby
bundle exec bundler-audit check

A sample report shows CVE references:

ruby
Name: rails
Version: 6.1.3
Advisory: CVE-2021-22885
Criticality: High
URL: https://nvd.nist.gov/vuln/detail/CVE-2021-22885
Title: Possible Information Disclosure / UnMasking of Tokens in Rails
Solution: upgrade to >= 6.1.4.1, ~> 6.0.3.7

This tells you exactly which gem, which version, and what to upgrade to. In most cases, updating is straightforward:

ruby
bundle update rails

Sometimes a gem hasn't been patched yet. In those cases, you can ignore the advisory temporarily by creating a bundler-audit config file:

ruby
# .bundler-audit.yml
ignore:
  - CVE-2021-22885
  - CVE-2020-15169

However, treat ignores as a stopgap. Document why, set a reminder to check for patches, and plan the upgrade. Never ignore indefinitely.

Run both in CI to block risky merges

Scanning locally is good, but developers might skip checks or disable warnings. CI automation ensures every pull request is vetted before merging.

Add a CI step for Brakeman. For GitHub Actions:

ruby
name: Security
on: [pull_request]

jobs:
  brakeman:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: 3.2
          bundler-cache: true
      - name: Run Brakeman
        run: bundle exec brakeman --no-summary --exit-on-warn
  
  bundler-audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: 3.2
          bundler-cache: true
      - name: Run bundler-audit
        run: bundle exec bundler-audit check --update

The --exit-on-warn flag tells Brakeman to exit with a non-zero code if any warnings exist, which fails the CI build. Similarly, bundler-audit returns a failure code if vulnerabilities are found. This prevents merging unless the team explicitly addresses findings.

Create a security checklist in your PR template to remind developers:

ruby
## Security
- [ ] Brakeman passes
- [ ] bundler-audit passes
- [ ] No new gems added without review
- [ ] No hardcoded secrets or credentials

Fix findings before merging

When a scan fails, the approach depends on the finding's nature.

Critical vulnerabilities in dependencies: upgrade immediately or create a patch-level fix. For example, if Rails 6.1.3 has a remote code execution bug, update to 6.1.4 without hesitation.

Code issues in Brakeman: often require refactoring. A mass assignment warning might need strong parameters; an unsafe redirect might need whitelisting. These changes typically involve a few lines of code and take 10–20 minutes.

False positives: Brakeman sometimes flags code that's actually safe. For instance, it might warn about a redirect even though you've already validated the destination. In these cases, add a comment to suppress the warning:

ruby
def dashboard
  # Brakeman false positive: next_page is validated against SAFE_ROUTES
  redirect_to validated_next_page
end

However, use suppressions sparingly. If you're suppressing many warnings, it signals that your security practices need review.

Common mistakes

Ignoring low-confidence warnings: Brakeman assigns confidence levels to its findings. Low-confidence warnings are less reliable but shouldn't be ignored blindly. Read the output and assess whether the pattern applies to your code. Sometimes a low-confidence finding reveals a real vulnerability.

Not updating bundler-audit's database: The bundler-audit gem ships with a database of known CVEs, but this data goes stale. Always run bundle exec bundler-audit update before committing, or ensure your CI does this automatically.

Suppressing instead of fixing: It's tempting to ignore findings with config files, but this pushes debt forward. Fix or justify every finding on its own merits. If you're ignoring dozens of warnings, your codebase has deeper security issues that deserve attention.

Running scans only locally: Developers forget. CI automation is the enforcer. Make it a gate: no merge without passing scans.

Treating security as a one-time task: Brakeman and bundler-audit are continuous tools. Run them on every PR, every release, and at least monthly in production to catch new CVEs in your current dependencies.

Key takeaways

  • Brakeman performs static analysis of your Rails code to catch SQL injection, mass assignment, unsafe redirects, and other OWASP-class issues.
  • bundler-audit cross-references your Gemfile.lock against a database of known CVEs, alerting you to vulnerable gem versions.
  • Integrate both tools into your CI pipeline with exit codes that block merges; this enforces security before code reaches main.
  • Address findings promptly: upgrade dependencies, refactor unsafe code, or document justified exceptions.
  • Avoid suppressing findings without cause; use suppressions as temporary measures only when a fix is planned and tracked.
  • Update bundler-audit's CVE database frequently, especially in CI, to catch newly disclosed vulnerabilities in your existing dependencies.
  • Security scanning is fast, automated, and non-invasive; there's no reason not to run these tools on every pull request.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles