CrackedRuby logo

CrackedRuby

Brakeman

Overview

Brakeman analyzes Ruby on Rails applications to identify security vulnerabilities without executing the code. The scanner examines Rails models, views, controllers, and configuration files to detect common security flaws including SQL injection, cross-site scripting, command injection, and mass assignment vulnerabilities.

The tool operates as a command-line utility that parses Rails application code and generates reports in multiple formats. Brakeman maintains an internal representation of the application structure, tracking data flow through methods and identifying potentially dangerous code patterns.

# Basic Brakeman scan
require 'brakeman'

# Scan current directory
results = Brakeman.run('.')
puts results.warnings.length
# => 5

Brakeman categorizes findings by confidence levels: High, Medium, and Weak. High confidence warnings typically indicate genuine security issues, while Medium and Weak confidence warnings may include false positives that require manual review.

# Programmatic usage with options
tracker = Brakeman.run(
  app_path: '/path/to/rails/app',
  output_formats: [:json, :html],
  confidence_levels: [1, 2] # High and Medium only
)

tracker.warnings.each do |warning|
  puts "#{warning.warning_type}: #{warning.message}"
end

The scanner supports Rails applications from version 2.3 through current versions, automatically detecting the Rails version and adjusting its analysis accordingly. Brakeman recognizes Rails conventions and patterns, understanding how data flows through typical Rails architectural components.

# Filtering warnings by type
scanner_results = Brakeman.run('/app')
sql_warnings = scanner_results.warnings.select do |w|
  w.warning_type == 'SQL Injection'
end

sql_warnings.each do |warning|
  puts "File: #{warning.file}"
  puts "Line: #{warning.line}"
  puts "Code: #{warning.format_code}"
end

Basic Usage

Install Brakeman as a gem and run it against Rails applications from the command line or integrate it programmatically into Ruby scripts. The simplest usage involves running the scanner against the current directory containing a Rails application.

gem install brakeman
cd /path/to/rails/app
brakeman

The command-line interface provides numerous options for customizing scans. Common options include specifying output formats, confidence levels, and specific warning types to include or exclude.

# Generate JSON and HTML reports
brakeman -o results.json -f json
brakeman -o report.html -f html

# Only show high confidence warnings
brakeman -w1

# Skip certain warning types
brakeman -x "Mass Assignment,Cross Site Scripting"

Programmatic usage through the Ruby API allows integration into custom tools, test suites, and continuous integration pipelines. The Brakeman.run method accepts a hash of options corresponding to command-line flags.

require 'brakeman'

options = {
  app_path: Rails.root,
  output_formats: [:json],
  min_confidence: 2,
  skip_checks: ['MassAssignment']
}

results = Brakeman.run(options)

# Process warnings
results.warnings.each do |warning|
  next if warning.confidence > 1
  
  puts "Security Issue: #{warning.warning_type}"
  puts "Location: #{warning.file}:#{warning.line}"
  puts "Message: #{warning.message}"
  puts "Code: #{warning.format_code}"
  puts "---"
end

Configuration files allow persistent settings across scans. Brakeman reads configuration from .brakeman.yml files in the application root directory, supporting YAML format for specifying scan options.

# .brakeman.yml
:skip_checks:
  - MassAssignment
  - LinkTo
:min_confidence: 2
:output_formats:
  - json
  - html
:output_files:
  - brakeman-report.json
  - brakeman-report.html

The scanner processes various file types within Rails applications, including Ruby source files, ERB templates, Haml templates, and YAML configuration files. Brakeman automatically excludes vendor directories, test directories, and other non-application code by default.

# Custom file inclusion/exclusion
options = {
  app_path: '.',
  skip_files: ['app/controllers/admin_controller.rb'],
  only_files: ['app/models/', 'app/controllers/'],
  rails_version: '6.1'
}

tracker = Brakeman.run(options)
puts "Scanned #{tracker.processed_files.length} files"

Error Handling & Debugging

Brakeman warnings require careful interpretation to distinguish genuine security vulnerabilities from false positives. Each warning includes context information including file location, line numbers, affected code, and confidence levels that aid in evaluation.

def analyze_warning_severity(warning)
  case warning.confidence
  when 0
    "High - Likely genuine vulnerability"
  when 1
    "Medium - Review required"
  when 2
    "Weak - May be false positive"
  else
    "Unknown confidence level"
  end
end

results = Brakeman.run('.')
results.warnings.each do |warning|
  severity = analyze_warning_severity(warning)
  puts "#{warning.warning_type} (#{severity})"
  puts "  File: #{warning.relative_path}"
  puts "  Method: #{warning.method}" if warning.method
  puts "  User input: #{warning.user_input}" if warning.user_input
end

False positives occur when Brakeman identifies potentially dangerous patterns that are actually safe in context. Common scenarios include sanitized user input, whitelisted parameters, and protected method calls that appear unsafe to static analysis.

# Handling false positives programmatically
def filter_false_positives(warnings)
  warnings.reject do |warning|
    # Skip warnings in test files
    next true if warning.file.include?('/spec/') || warning.file.include?('/test/')
    
    # Skip known safe patterns
    if warning.warning_type == 'SQL Injection'
      # Allow sanitized queries
      next true if warning.format_code.include?('sanitize_sql')
    end
    
    false
  end
end

filtered_warnings = filter_false_positives(results.warnings)

Missing dependencies or unsupported Rails versions can cause scan failures. Brakeman requires access to Rails models and their associations to perform accurate analysis, failing when essential files are missing or unreadable.

begin
  results = Brakeman.run(
    app_path: suspect_app_path,
    ensure_latest: false,
    quiet: true
  )
rescue Brakeman::NoApplication
  puts "No Rails application found at #{suspect_app_path}"
  exit 1
rescue StandardError => e
  puts "Scan failed: #{e.message}"
  puts "Backtrace: #{e.backtrace.first(5).join("\n")}"
  exit 1
end

Large codebases may encounter memory constraints or performance issues during scanning. Brakeman loads the entire application into memory for analysis, requiring sufficient RAM for complex applications with many models and controllers.

# Monitor scan performance
start_time = Time.now
memory_before = `ps -o rss= -p #{Process.pid}`.to_i

results = Brakeman.run(
  app_path: large_app_path,
  parallel_checks: true
)

memory_after = `ps -o rss= -p #{Process.pid}`.to_i
duration = Time.now - start_time

puts "Scan completed in #{duration.round(2)} seconds"
puts "Memory usage: #{(memory_after - memory_before) / 1024} MB"
puts "Warnings found: #{results.warnings.length}"

Template parsing errors occur when Brakeman encounters unsupported template syntax or malformed ERB code. These failures typically result in incomplete scans rather than total failures, with error details available in the scan results.

results = Brakeman.run('.')

if results.errors.any?
  puts "Scan completed with errors:"
  results.errors.each do |error|
    puts "  #{error[:error]}: #{error[:file]}"
    puts "    #{error[:exception]}" if error[:exception]
  end
end

if results.ignored_files.any?
  puts "Ignored files:"
  results.ignored_files.each { |file| puts "  #{file}" }
end

Production Patterns

Continuous integration environments commonly integrate Brakeman scans into automated testing pipelines, failing builds when high-confidence security vulnerabilities are detected. Exit codes indicate scan results, with non-zero codes signaling discovered vulnerabilities.

# CI/CD integration script
#!/usr/bin/env ruby
require 'brakeman'
require 'json'

def ci_security_scan
  results = Brakeman.run(
    app_path: ENV['RAILS_ROOT'] || '.',
    output_formats: [:json],
    min_confidence: 1, # High and Medium confidence only
    exit_on_warn: true
  )
  
  high_confidence_warnings = results.warnings.select { |w| w.confidence == 0 }
  
  if high_confidence_warnings.any?
    puts "SECURITY SCAN FAILED"
    puts "High confidence vulnerabilities found:"
    high_confidence_warnings.each do |warning|
      puts "  #{warning.warning_type}: #{warning.file}:#{warning.line}"
    end
    exit 1
  else
    puts "Security scan passed"
    exit 0
  end
end

ci_security_scan if __FILE__ == $0

Docker containers and deployment environments require careful configuration to ensure Brakeman can access application code and dependencies. Scanning during the build process catches vulnerabilities before production deployment.

# Dockerfile security scanning stage
FROM ruby:3.0 AS security_scanner
WORKDIR /app
COPY Gemfile Gemfile.lock ./
RUN bundle install --without development test

COPY . .
RUN gem install brakeman
RUN brakeman --exit-on-warn --confidence-level 1 --format json --output /tmp/security-report.json

# Copy security report to final image
FROM ruby:3.0-alpine AS production
COPY --from=security_scanner /tmp/security-report.json /app/security-report.json

Team workflows often establish baseline security reports to track improvements and regressions over time. Comparing current scans against baseline reports identifies newly introduced vulnerabilities.

class SecurityBaseline
  def initialize(baseline_file)
    @baseline = JSON.parse(File.read(baseline_file))
  end
  
  def compare_with_current_scan
    current_results = Brakeman.run('.')
    current_warnings = current_results.warnings.map(&:fingerprint)
    baseline_warnings = @baseline['warnings'].map { |w| w['fingerprint'] }
    
    new_warnings = current_warnings - baseline_warnings
    fixed_warnings = baseline_warnings - current_warnings
    
    {
      new_vulnerabilities: new_warnings.length,
      fixed_vulnerabilities: fixed_warnings.length,
      regression: new_warnings.any?
    }
  end
end

# Usage in CI pipeline
baseline = SecurityBaseline.new('security-baseline.json')
comparison = baseline.compare_with_current_scan

if comparison[:regression]
  puts "Security regression detected: #{comparison[:new_vulnerabilities]} new issues"
  exit 1
end

Monitoring systems can integrate Brakeman scan results to track security posture across multiple applications and deployments. Aggregating scan data provides organizational security metrics and trend analysis.

require 'net/http'
require 'json'

class SecurityMetrics
  def initialize(metrics_endpoint)
    @endpoint = URI(metrics_endpoint)
  end
  
  def report_scan_results(app_name, results)
    metrics = {
      application: app_name,
      timestamp: Time.now.iso8601,
      total_warnings: results.warnings.length,
      high_confidence: results.warnings.count { |w| w.confidence == 0 },
      medium_confidence: results.warnings.count { |w| w.confidence == 1 },
      warning_types: results.warnings.group_by(&:warning_type).transform_values(&:length)
    }
    
    http = Net::HTTP.new(@endpoint.host, @endpoint.port)
    request = Net::HTTP::Post.new(@endpoint.path)
    request['Content-Type'] = 'application/json'
    request.body = metrics.to_json
    
    response = http.request(request)
    puts "Metrics reported: #{response.code}"
  end
end

# Automated reporting
results = Brakeman.run('.')
metrics = SecurityMetrics.new(ENV['SECURITY_METRICS_URL'])
metrics.report_scan_results(ENV['APP_NAME'], results)

Common Pitfalls

Mass assignment warnings frequently trigger false positives in Rails applications that properly configure strong parameters. Brakeman may not recognize complex parameter filtering patterns, particularly in controller inheritance hierarchies or shared parameter handling modules.

# False positive scenario - Brakeman may not detect strong parameters
class ArticlesController < ApplicationController
  before_action :set_article_params, only: [:create, :update]
  
  def create
    # Brakeman might flag this as mass assignment vulnerability
    @article = Article.new(@article_params)
  end
  
  private
  
  def set_article_params
    # Complex parameter filtering that Brakeman might miss
    @article_params = params.require(:article).permit(
      permitted_article_attributes
    )
  end
  
  def permitted_article_attributes
    base_attrs = [:title, :content, :published]
    base_attrs += [:featured] if current_user.admin?
    base_attrs
  end
end

SQL injection warnings often appear for parameterized queries that are actually safe. Dynamic query building using ActiveRecord methods may trigger warnings even when properly escaping user input through Rails built-in sanitization.

# Safe code that may trigger SQL injection warnings
class ReportGenerator
  def self.generate_report(filters)
    query = Article.all
    
    # Brakeman might flag these as SQL injection risks
    query = query.where("published_at > ?", filters[:start_date]) if filters[:start_date]
    query = query.where("category_id IN (?)", filters[:categories]) if filters[:categories]
    
    # This is safe - Rails sanitizes the interpolated values
    # But Brakeman static analysis might not recognize the safety
    query
  end
end

Cross-site scripting warnings may flag legitimate HTML output in administrative interfaces or areas where HTML content is intentionally rendered. The raw and html_safe methods trigger warnings even when the content source is trusted.

# Administrative interface that may trigger XSS warnings
class AdminDashboard
  def system_status_html
    status_message = SystemHealthCheck.current_status
    
    # Brakeman flags this as potential XSS
    # Even though admin content is trusted
    content_tag :div, status_message.html_safe, class: 'system-status'
  end
  
  def render_user_content(content)
    # This legitimately needs review - user content should be sanitized
    # Brakeman correctly identifies this as risky
    content.html_safe
  end
end

Command injection warnings appear for legitimate system calls that don't involve user input. File system operations, backup scripts, and system integrations may trigger warnings when commands are constructed using safe, internal data sources.

class BackupManager
  def perform_backup(database_name)
    # Brakeman may flag this as command injection
    # Even though database_name comes from configuration
    timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
    backup_file = "/backups/#{database_name}_#{timestamp}.sql"
    
    # This is safe - no user input involved
    # But static analysis cannot always determine data source safety
    system("pg_dump #{database_name} > #{backup_file}")
  end
end

Version-specific warnings may not apply to the actual Rails version in use. Brakeman sometimes fails to detect the correct Rails version or applies warnings for vulnerability patterns that were fixed in newer versions.

# Check Rails version compatibility
def validate_brakeman_warnings(warnings)
  rails_version = Rails::VERSION::STRING
  
  warnings.reject do |warning|
    # Skip warnings for older Rails versions
    case warning.warning_code
    when 'CVE-2019-5418' # File Content Disclosure
      Gem::Version.new(rails_version) >= Gem::Version.new('5.2.2.1')
    when 'CVE-2020-8164' # Deserialization of Untrusted Data
      Gem::Version.new(rails_version) >= Gem::Version.new('6.0.3.1')
    else
      false
    end
  end
end

Configuration inheritance across multiple Rails engines or mounted applications can confuse Brakeman's analysis. The scanner may not properly understand security configurations that apply across engine boundaries or shared authentication systems.

# Complex engine setup that may confuse Brakeman
class MainApp::Application < Rails::Application
  # Security configurations that apply to mounted engines
  config.force_ssl = true
  config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
end

# Mounted engine that inherits security settings
module AdminEngine
  class Engine < ::Rails::Engine
    # Brakeman might not recognize inherited security configurations
    # and flag admin controllers as lacking CSRF protection
  end
end

Reference

Command Line Options

Option Description Example
-o, --output FILE Write output to specified file brakeman -o report.html
-f, --format FORMAT Output format (text, html, json, csv, tabs, sarif) brakeman -f json
-w, --confidence-level N Minimum confidence level (1=high, 2=medium, 3=weak) brakeman -w 2
-x, --skip-checks CHECK1,CHECK2 Skip specified checks brakeman -x "MassAssignment,SSL"
-t, --test CHECK1,CHECK2 Run only specified checks brakeman -t "SQL,XSS"
--skip-files FILE1,FILE2 Skip scanning specified files brakeman --skip-files "admin.rb,*.erb"
--rails-version VERSION Specify Rails version brakeman --rails-version 6.1
-q, --quiet Suppress informational warnings brakeman -q
--no-exit-on-warn Return exit code 0 even with warnings brakeman --no-exit-on-warn
--compare FILE Compare results against baseline brakeman --compare baseline.json

Programmatic API Methods

Method Parameters Returns Description
Brakeman.run(options) options (Hash) Tracker Execute security scan with specified options
#warnings None Array<Warning> Array of all detected warnings
#errors None Array<Hash> Parsing errors encountered during scan
#ignored_files None Array<String> Files skipped during analysis
#filtered_warnings None Array<Warning> Warnings after applying filters

Warning Object Properties

Property Type Description
warning_type String Type of security vulnerability detected
confidence Integer Confidence level (0=High, 1=Medium, 2=Weak)
file String Absolute path to file containing vulnerability
line Integer Line number where vulnerability occurs
message String Descriptive message about the vulnerability
code Sexp Abstract syntax tree of problematic code
method String Method name containing vulnerability
user_input Sexp Source of potentially malicious user input
fingerprint String Unique identifier for tracking warnings across scans

Common Warning Types

Warning Type Description Common Causes
SQL Injection Unsafe database queries String interpolation in queries, unsafe where clauses
Cross Site Scripting Unescaped output in views Using raw, html_safe, or bypassing escaping
Mass Assignment Unrestricted parameter assignment Missing strong parameters, bulk assignment
Command Injection Unsafe system command execution User input in system, backticks, or exec calls
File Access Unauthorized file system access User-controlled file paths
Session Setting Insecure session configuration Missing secure flags, weak session keys
Cross-Site Request Forgery Missing CSRF protection Controllers without protect_from_forgery
Redirect Open redirect vulnerabilities User-controlled redirect targets
Link To Dangerous link generation User input in link helpers
Render Unsafe template rendering Dynamic template selection

Configuration File Format

# .brakeman.yml
:skip_checks:
  - MassAssignment
  - LinkTo
:skip_files:
  - "app/controllers/admin_controller.rb"
  - "lib/legacy/*.rb"
:min_confidence: 2
:output_formats:
  - json
  - html
:output_files:
  - brakeman-report.json
  - brakeman-report.html
:print_report: true
:rails_version: "6.1"
:exit_on_warn: true

Exit Codes

Exit Code Meaning
0 No warnings found or --no-exit-on-warn specified
3 Warnings found (when --exit-on-warn is default behavior)
4 Brakeman execution error
5 No Rails application found