Automating Security Audits for Legacy Ruby on Rails Codebases
In 1970, the United States Department of Defense published the Ware Report — authored by Willis Ware — an extensive evaluation of computer security within defense systems. This report highlighted a fundamental truth that remains relevant: as systems grow in complexity, the surface area for potential attacks expands proportionally. Early manual audits of these systems were laborious, requiring teams of engineers to review punch cards and magnetic tapes line by line to ensure system integrity.
Today, we face a similar — though vastly accelerated — challenge with legacy software. A large and complex Ruby on Rails application can contain hundreds of thousands of lines of code, and that doesn’t even count the hundreds of third-party dependencies pulled in via RubyGems. When an organization faces an immediate compliance audit, or requires urgent identification of critical security vulnerabilities, manually reviewing every line of code is not feasible.
Instead, we must rely on automation. Specifically, we need to generate a reliable, automated Rails security audit report that identifies vulnerable dependencies and code vulnerabilities before they ever reach the production environment.
Before we get into the technical implementation, though, let’s take a step back and examine the structural philosophy of auditing legacy codebases.
The Architecture of a Security Audit
Security risks in a legacy application generally fall into two broad categories: vulnerabilities in the code we write, and vulnerabilities in the code other people write (our dependencies).
When organizations fall behind on their Ruby and Rails upgrades, they accumulate technical debt. This technical debt often manifests as outdated, vulnerable dependencies. Attack vectors multiply as these older libraries are discovered to have flaws, and without a reliable maintenance routine, these flaws remain unpatched.
There are, of course, many ways to approach a security audit. For organizations looking to secure a legacy codebase, however, establishing an automated baseline is the most pragmatic first step. We will examine two primary tools that should be part of every Rails application’s continuous integration pipeline: Brakeman and Bundler-Audit.
Static Analysis with Brakeman
Strictly speaking, Brakeman is a static analysis vulnerability scanner designed specifically for Ruby on Rails applications. Rather than running your application and probing it for weaknesses from the outside — a technique known as dynamic analysis — Brakeman parses your application’s source code and analyzes the resulting abstract syntax tree for known security anti-patterns.
Usage
Installing and running Brakeman is straightforward. In its most basic form, you can run it directly in your application’s root directory:
$ gem install brakeman
$ brakeman
When you run this command on a legacy application, you will likely see a significant amount of output detailing potential cross-site scripting (XSS) vulnerabilities, SQL injection risks, and mass assignment flaws. The output looks similar to this:
== Brakeman Report ==
Application Path: /opt/legacy_app
Rails Version: 5.2.8.1
Brakeman Version: 6.1.1
Scan Date: 2026-03-16 10:00:00 -0400
...snip...
== Warnings ==
Confidence: High
Category: SQL Injection
Check: SQL
Message: Possible SQL injection
Code: User.where("email = '#{params[:email]}'")
File: app/controllers/users_controller.rb
Line: 14
...snip...
You also may notice something else: a large number of false positives. Static analysis tools must guess at the context of certain variables, and they tend to err on the side of caution. This inclusivity, while safer, can be overwhelming.
We can manage these false positives by generating an ignore file. Brakeman provides an interactive mode for this:
$ brakeman -I
This command will prompt you to review each warning and decide whether to ignore it. Brakeman will then generate a brakeman.ignore file. This is significant, because it means we can configure our CI pipeline to fail only when new, un-ignored vulnerabilities are introduced, effectively halting the accumulation of new security debt.
Tip: While we installed Brakeman directly with
gem installin the example above, you will typically want to add it to yourGemfileand run it viabundle exec brakemanto ensure everyone on your team is using the exact same version of the scanner.
Managing Vulnerable Dependencies with Bundler-Audit
Our application code is only part of the equation. Often, the most severe security risks lie in the libraries we depend upon. As a legacy application ages, its Gemfile.lock becomes a historical artifact of outdated software.
To automate the detection of these vulnerable dependencies, we use Bundler-Audit. This tool checks your Gemfile.lock against the Ruby Advisory Database, a community-maintained repository of known security vulnerabilities in Ruby gems.
There are, of course, alternative tools for this task; Dependabot and Snyk are widely used commercial offerings that automate dependency scanning. However, Bundler-Audit is an excellent foundational tool that requires no external service subscriptions and can be run entirely offline once the database is fetched.
Usage
We can install and run Bundler-Audit like this:
$ gem install bundler-audit
$ bundle audit check --update
The --update flag is crucial here; it ensures that Bundler-Audit fetches the latest vulnerability data before scanning your project.
If Bundler-Audit detects a vulnerability, it will exit with a non-zero status code and print a report detailing the specific gem, the CVE (Common Vulnerabilities and Exposures) identifier, and the patched versions:
$ bundle audit check --update
Fetching gem metadata from https://rubygems.org/
...snip...
Name: nokogiri
Version: 1.10.9
Advisory: CVE-2020-26247
Criticality: High
URL: https://github.com/sparklemotion/nokogiri/security/advisories/GHSA-xxx
Title: XML external entity vulnerability in Nokogiri
Solution: upgrade to >= 1.11.0.rc4
Vulnerabilities found!
This provides clear, actionable intelligence for dependency management, telling you exactly which gems require gradual version bumps to remediate the risk.
Integrating Continuous Auditing
A security audit is not a one-time event; it is an ongoing process. To effectively secure a production environment, these automated checks must be integrated into your continuous integration (CI) pipeline.
Often, this is used in automated scripts with configuration similar to the following GitHub Actions snippet:
name: Security Audit
on: [push, pull_request]
jobs:
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 Brakeman
run: bundle exec brakeman -z
- name: Run Bundler-Audit
run: |
gem install bundler-audit
bundle audit check --update
Note the use of the -z flag with Brakeman in the script above. This flag tells Brakeman to exit with a non-zero status code if any warnings are found (excluding those in the ignore file), effectively failing the build and preventing the vulnerable code from being deployed.
The Limitations of Automation
Automation is powerful, but it is not a panacea.
While tools like Brakeman and Bundler-Audit are excellent at identifying known technical flaws — such as an outdated Nokogiri gem or an unescaped string in an ERB template — they lack business context. For example, if your application contains a flaw in its authorization logic that allows a standard user to view an administrator’s dashboard, neither of these tools will flag it as an error. The code itself is syntactically sound; it is the logic that is flawed.
Therefore, while establishing automated security scanning is a necessary baseline, it is rarely sufficient on its own. For large and complex applications, particularly those handling sensitive data or facing compliance requirements, a comprehensive security audit often requires human expertise to identify logic flaws, architectural weaknesses, and complex attack vectors that automated tools cannot perceive.
Ultimately, automating these routine checks frees up your engineering team — or external Ruby and Rails experts — to focus their attention on the complex, systemic security challenges that truly require human insight. By implementing these tools today, you take a significant, pragmatic step toward a healthier, more secure legacy application.
Sponsored by Durable Programming
Need help maintaining or upgrading your Ruby on Rails application? Durable Programming specializes in keeping Rails apps secure, performant, and up-to-date.
Hire Durable Programming