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:
group :development do
gem 'brakeman', require: false
endRun it from the command line:
bundle exec brakemanThis generates a report of findings ranked by confidence level. A typical output includes warnings like this:
[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:
def create
@user = User.new(user_params)
@user.save
end
private
def user_params
params.require(:user).permit(:email, :name)
endAnother common finding is SQL injection. Brakeman flags string interpolation in queries:
# 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:
# 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:
group :development do
gem 'bundler-audit', require: false
endRun it:
bundle exec bundler-audit checkA sample report shows CVE references:
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.7This tells you exactly which gem, which version, and what to upgrade to. In most cases, updating is straightforward:
bundle update railsSometimes a gem hasn't been patched yet. In those cases, you can ignore the advisory temporarily by creating a bundler-audit config file:
# .bundler-audit.yml
ignore:
- CVE-2021-22885
- CVE-2020-15169However, 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:
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 --updateThe --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:
## Security
- [ ] Brakeman passes
- [ ] bundler-audit passes
- [ ] No new gems added without review
- [ ] No hardcoded secrets or credentialsFix 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:
def dashboard
# Brakeman false positive: next_page is validated against SAFE_ROUTES
redirect_to validated_next_page
endHowever, 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.






Comments
Loading comments…