Overview
Security auditing examines applications, codebases, and infrastructure to detect vulnerabilities before attackers exploit them. The process combines automated tools that scan for known vulnerability patterns with manual code review to identify logic flaws, authentication issues, and business-specific security gaps that automated scanners miss.
Security audits fall into several categories. Static Application Security Testing (SAST) analyzes source code without executing it, detecting issues like SQL injection vulnerabilities, hardcoded credentials, and insecure cryptographic implementations. Dynamic Application Security Testing (DAST) runs the application and probes it for vulnerabilities by sending crafted inputs and observing responses. Dependency auditing checks third-party libraries for known CVEs (Common Vulnerabilities and Exposures). Infrastructure auditing reviews server configurations, network rules, and deployment settings.
Ruby applications require security auditing across multiple layers. The application code itself may contain vulnerabilities in authentication logic, authorization checks, or input handling. Rails applications add framework-specific concerns like mass assignment vulnerabilities and CSRF protection. Ruby gems introduce supply chain risks when dependencies contain vulnerabilities or malicious code. The deployment environment contributes additional risk through misconfigured servers, exposed secrets, or inadequate access controls.
# Example vulnerability that security auditing detects
class UsersController < ApplicationController
def update
# Mass assignment vulnerability - any attribute can be modified
@user = User.find(params[:id])
@user.update(params[:user])
end
def search
# SQL injection vulnerability
query = "SELECT * FROM users WHERE name = '#{params[:name]}'"
User.find_by_sql(query)
end
end
Security auditing operates continuously throughout the development lifecycle. Pre-commit hooks run basic checks before code enters version control. CI/CD pipelines execute comprehensive scans on every pull request. Scheduled audits check deployed applications for newly discovered vulnerabilities in dependencies. Periodic manual reviews examine business logic and access control implementations that automated tools cannot fully assess.
Key Principles
Defense in Depth structures security in multiple independent layers. Security auditing checks each layer separately rather than assuming any single control will prevent all attacks. An audit verifies that authentication, authorization, input validation, output encoding, and logging all function correctly. When one control fails, others contain the damage.
Least Privilege requires that code, users, and processes operate with minimal permissions. Audits verify that database users cannot drop tables, service accounts cannot access unrelated resources, and API endpoints enforce proper authorization. The principle extends to file permissions, network access, and third-party integrations.
Fail Secure ensures that errors and edge cases default to denying access rather than granting it. Security audits test error handling paths to confirm that exceptions do not bypass authentication, configuration failures block requests rather than allow them, and validation errors reject input rather than processing it with default assumptions.
Regular Cadence makes security auditing routine rather than reactive. Continuous scanning detects issues immediately after introduction. Weekly or monthly comprehensive audits catch accumulated issues and verify that security controls remain effective as the codebase evolves. Annual penetration tests validate the entire security posture against realistic attack scenarios.
Automation with Human Oversight balances efficiency with accuracy. Automated tools scan large codebases quickly and consistently detect known vulnerability patterns. Manual review identifies business logic flaws, verifies that security controls work correctly in context, and filters false positives that automated scanners generate. The combination provides comprehensive coverage.
Risk-Based Prioritization focuses effort on the most critical vulnerabilities. Not all findings require immediate remediation. Critical issues like authentication bypasses or remote code execution warrant emergency fixes. Medium-severity issues like information disclosure need prompt attention. Low-severity findings may be acceptable risks depending on business context and mitigation difficulty.
Verification and Validation confirms that identified vulnerabilities are genuine and that fixes resolve them completely. False positives waste remediation effort. Incomplete fixes leave vulnerabilities exploitable through alternate paths. Security audits include validation steps that reproduce vulnerabilities, verify exploitability, and confirm that patches eliminate the underlying issue.
Contextual Analysis recognizes that vulnerability severity depends on usage. A cross-site scripting vulnerability in an admin interface accessed by trusted users differs from the same vulnerability in public-facing forms. Auditors assess exposure, data sensitivity, and available compensating controls when evaluating risk.
Ruby Implementation
Ruby security auditing uses a combination of static analysis tools, dependency scanners, and runtime security checks. The Ruby ecosystem provides several mature tools specifically designed for security analysis of Ruby applications.
Static analysis examines Ruby code for vulnerability patterns without executing it. Brakeman is the primary tool for Rails applications, scanning for SQL injection, cross-site scripting, mass assignment, and dozens of other vulnerability types. It understands Rails-specific patterns and idioms, reducing false positives compared to generic code scanners.
# Gemfile for security auditing tools
gem 'brakeman', group: :development
gem 'bundler-audit', group: :development
gem 'ruby_audit', group: :development
gem 'rubocop', group: :development
gem 'rubocop-rails', group: :development
Brakeman runs from the command line and integrates into CI/CD pipelines. It analyzes controller code, model associations, view templates, and routes to identify vulnerabilities.
# Running Brakeman with different output formats
system('brakeman') # Interactive output
system('brakeman -o brakeman-report.html') # HTML report
system('brakeman -f json -o brakeman.json') # JSON for CI integration
system('brakeman --confidence-level 2') # Only high-confidence warnings
Dependency auditing checks Ruby gems for known vulnerabilities using bundler-audit and ruby-audit. These tools compare installed gem versions against vulnerability databases and report any matches.
# Auditing dependencies
require 'bundler/audit/task'
Bundler::Audit::Task.new
# In Rakefile
task default: 'bundle:audit'
# Running dependency audit
system('bundle audit check --update') # Updates database and checks
system('bundle audit update') # Updates vulnerability database only
Custom security checks integrate into RuboCop, Ruby's static code analyzer. Security-focused cops detect patterns like hardcoded secrets, weak cryptography, and dangerous method calls.
# .rubocop.yml with security cops
require:
- rubocop-rails
Security/Eval:
Enabled: true
Security/Open:
Enabled: true
Security/MarshalLoad:
Enabled: true
Security/YAMLLoad:
Enabled: true
Runtime security monitoring instruments Ruby applications to detect and block attacks during execution. Rack middleware can validate inputs, enforce rate limits, and log suspicious requests.
# Rack middleware for security monitoring
class SecurityAuditMiddleware
def initialize(app)
@app = app
end
def call(env)
request = Rack::Request.new(env)
# Log potentially malicious patterns
if sql_injection_pattern?(request.params)
SecurityLogger.warn("SQL injection attempt detected",
ip: request.ip,
params: request.params)
end
if xss_pattern?(request.params)
SecurityLogger.warn("XSS attempt detected",
ip: request.ip,
params: request.params)
end
@app.call(env)
end
private
def sql_injection_pattern?(params)
params.any? do |_, value|
value.to_s.match?(/(\bUNION\b|\bSELECT\b.*\bFROM\b|\bDROP\b)/i)
end
end
def xss_pattern?(params)
params.any? do |_, value|
value.to_s.match?(/<script|javascript:|onerror=/i)
end
end
end
Security auditing extends to Rails console sessions and rake tasks that access sensitive data. Audit logging tracks who accessed what data and when.
# Auditing Rails console access
module ConsoleAuditLogger
def self.start
Rails.logger.info("Console session started by #{ENV['USER']} at #{Time.current}")
# Log all executed commands
IRB.conf[:PROMPT][:AUDIT] = {
PROMPT_I: proc { |context|
Rails.logger.info("Console command executed")
"#{Rails.env}> "
}
}
end
end
# In config/application.rb
console do
ConsoleAuditLogger.start
end
Automated security testing incorporates security assertions into the test suite. Tests verify that security controls function correctly and that known vulnerability patterns are absent.
# Security test examples
require 'test_helper'
class SecurityTest < ActionDispatch::IntegrationTest
test "prevents unauthorized access to admin area" do
get admin_dashboard_path
assert_redirected_to login_path
end
test "rejects SQL injection attempts" do
get search_path, params: { query: "'; DROP TABLE users--" }
assert_response :success
assert_equal User.count, 5 # Verify users table still exists
end
test "prevents mass assignment of privileged attributes" do
user = users(:basic_user)
post user_path(user), params: { user: { role: 'admin' } }
user.reload
refute_equal 'admin', user.role
end
test "enforces HTTPS in production" do
skip unless Rails.env.production?
assert Rails.application.config.force_ssl
end
end
Security Implications
Security auditing focuses on specific vulnerability categories that commonly affect Ruby applications. Understanding these categories guides both automated tool configuration and manual review priorities.
Injection Vulnerabilities occur when untrusted input reaches an interpreter without proper sanitization. SQL injection remains prevalent despite parameterized queries being standard practice. Audits check for string interpolation in queries, dynamic table or column names, and raw SQL execution. Command injection affects system calls, file operations, and external process execution. LDAP, NoSQL, and email header injection follow similar patterns.
# Vulnerable to SQL injection
def find_users_by_name(name)
User.where("name = '#{name}'") # String interpolation in SQL
end
# Secure parameterized query
def find_users_by_name(name)
User.where("name = ?", name)
end
# Vulnerable to command injection
def process_file(filename)
system("convert #{filename} output.png")
end
# Secure with explicit arguments
def process_file(filename)
system("convert", filename, "output.png")
end
Authentication and Session Management flaws allow attackers to impersonate users or maintain unauthorized access. Audits verify that passwords are hashed with bcrypt or argon2, not MD5 or SHA1. Session tokens must be cryptographically random, not sequential or predictable. Sessions should expire after inactivity and regenerate after authentication state changes. Password reset tokens need secure generation and single-use enforcement.
Authorization Bypasses grant access to resources without proper permission checks. Audits trace request flows to verify authorization checks occur before data access. Direct object references must be validated. Parameter tampering should not escalate privileges. Authorization logic should be centralized rather than scattered across controllers.
# Authorization bypass vulnerability
def show
@document = Document.find(params[:id]) # Missing authorization check
end
# Proper authorization
def show
@document = current_user.documents.find(params[:id]) # Scoped to user's documents
# OR
@document = Document.find(params[:id])
authorize @document # Using Pundit or similar authorization framework
end
Cross-Site Scripting (XSS) executes attacker-controlled JavaScript in victim browsers. Rails automatically escapes output by default, but audits check for html_safe, raw, and <%== %> usage. User-controlled content in JSON responses, JavaScript templates, and HTML attributes requires context-appropriate encoding. Content Security Policy headers provide defense-in-depth.
Cross-Site Request Forgery (CSRF) tricks authenticated users into submitting malicious requests. Rails includes CSRF protection by default, but audits verify it remains enabled and that API endpoints requiring it don't bypass the protection. State-changing operations must reject requests lacking valid CSRF tokens.
Sensitive Data Exposure reveals information that aids attackers or harms users. Audits check for verbose error messages in production, detailed stack traces exposed to users, and credentials in logs or error reports. Database backups, log files, and temporary files need encryption at rest. API responses should not leak implementation details or enumerate valid usernames.
# Exposes sensitive information in logs
Rails.logger.info("User login: #{params[:email]} / #{params[:password]}")
# Secure logging filters sensitive parameters
# config/initializers/filter_parameter_logging.rb
Rails.application.config.filter_parameters += [:password, :password_confirmation, :secret]
# Results in: User login: [FILTERED] / [FILTERED]
Cryptographic Failures weaken security controls through improper encryption, weak randomness, or obsolete algorithms. Audits verify that SecureRandom generates tokens and identifiers, not rand. Encryption must use authenticated modes like AES-GCM, not ECB mode. TLS connections should enforce modern protocol versions and reject weak cipher suites.
Security Misconfigurations leave default credentials, enable debug features in production, or expose unnecessary services. Audits check that database URLs don't use default passwords, secret_key_base changes from the boilerplate, and debug toolbars don't load in production. Environment variables should override configuration defaults.
Tools & Ecosystem
The Ruby security ecosystem provides specialized tools for different auditing approaches. Each tool addresses specific vulnerability categories and integrates into development workflows at different stages.
Brakeman performs static analysis specifically for Rails applications. It understands Rails conventions and tracks data flow through controllers, models, and views. Brakeman detects over 30 vulnerability categories including SQL injection, command injection, XSS, CSRF, mass assignment, and redirect vulnerabilities. It generates confidence ratings for each warning based on how certain the detection is.
Brakeman runs quickly enough for pre-commit hooks and CI integration. It produces JSON output for automated processing and HTML reports for human review. Configuration files ignore false positives and adjust warning thresholds per project.
# brakeman.yml configuration
:skip_checks:
- LinkToHref # Project uses safe dynamic links
:ignore_warnings:
- filename: app/controllers/legacy_controller.rb
line: 42
fingerprint: a1b2c3d4e5f6g7h8
note: "False positive - input is validated by upstream middleware"
:confidence_threshold: 2 # Only report high and medium confidence issues
bundler-audit checks Gemfile.lock against a database of known gem vulnerabilities. It reports CVE numbers, affected versions, and patched versions for each vulnerable gem. The tool updates its vulnerability database from rubysec/ruby-advisory-db on GitHub.
Running bundler-audit as a CI gate prevents vulnerable dependencies from reaching production. The tool fails builds when vulnerable gems are detected, forcing developers to update or mitigate before merging.
# Rakefile integration
require 'bundler/audit/task'
Bundler::Audit::Task.new
desc 'Run security audit on dependencies'
task :security_audit do
puts "Updating vulnerability database..."
system('bundle audit update')
puts "Checking for vulnerable dependencies..."
system('bundle audit check') || abort("Security vulnerabilities detected!")
end
ruby-audit provides similar functionality to bundler-audit but uses a different vulnerability database and detection approach. Running both tools provides more comprehensive coverage as they sometimes report different vulnerabilities.
RuboCop extends beyond style checking with security-focused cops. The Security department detects dangerous method calls like eval, send, Marshal.load, and YAML.load. Additional cops catch common pitfalls like Open3.capture3 without proper argument escaping.
# .rubocop.yml security configuration
Security/Eval:
Description: 'Detects use of eval which can execute arbitrary code'
Enabled: true
Security/JSONLoad:
Description: 'Prefers JSON.parse over JSON.load'
Enabled: true
Security/MarshalLoad:
Description: 'Detects Marshal.load which can execute arbitrary code during deserialization'
Enabled: true
Security/Open:
Description: 'Detects Kernel.open which is susceptible to command injection'
Enabled: true
Safe: true
Security/YAMLLoad:
Description: 'Prefers YAML.safe_load over YAML.load'
Enabled: true
rack-attack provides runtime security controls through Rack middleware. It implements rate limiting, request throttling, IP allowlisting and blocklisting, and can block requests matching attack patterns. While not primarily an auditing tool, it logs suspicious activity for security analysis.
# config/initializers/rack_attack.rb
class Rack::Attack
# Throttle login attempts
throttle('login/email', limit: 5, period: 1.minute) do |req|
req.params['email'] if req.path == '/login' && req.post?
end
# Block suspicious IPs
blocklist('suspicious_ips') do |req|
BlockedIp.exists?(ip: req.ip)
end
# Log all blocked requests for audit
Rack::Attack.blocklisted_responder = lambda do |env|
SecurityAuditLog.create(
event: 'blocked_request',
ip: env['REMOTE_ADDR'],
path: env['PATH_INFO'],
blocked_at: Time.current
)
[403, {}, ['Forbidden']]
end
end
OWASP Dependency-Check scans project dependencies across multiple languages including Ruby. It provides more detailed vulnerability reporting than bundler-audit and integrates with enterprise security workflows.
Snyk offers commercial security scanning with deep Ruby and Rails support. It provides automated pull requests to fix vulnerabilities, prioritizes findings based on exploitability, and tracks remediation across teams. Snyk monitors deployed applications for newly disclosed vulnerabilities.
GitHub Security Advisories automatically scan repositories and create Dependabot alerts for vulnerable dependencies. Dependabot can automatically open pull requests to update vulnerable gems. Integration is automatic for GitHub-hosted repositories.
Integration strategies vary by tool and workflow phase. Pre-commit hooks run fast tools like RuboCop security cops. Pull request checks execute Brakeman and bundler-audit. Nightly builds run comprehensive scans including slower tools. Production monitoring uses runtime protection like rack-attack.
Practical Examples
Security auditing a Rails application requires systematic examination of authentication, authorization, input handling, and dependency security. The following examples demonstrate complete audit workflows.
Auditing Authentication Implementation
Authentication vulnerabilities allow unauthorized access to protected resources. A thorough audit examines password storage, session management, and credential transmission.
# Audit checklist for authentication
class AuthenticationAudit
def audit_password_storage
# Check that passwords use secure hashing
user_model = User.first
raise "No bcrypt detected" unless user_model.password_digest.start_with?('$2a$')
# Verify bcrypt cost factor is sufficient
cost = BCrypt::Password.new(user_model.password_digest).cost
raise "BCrypt cost too low: #{cost}" if cost < 12
puts "✓ Password storage uses bcrypt with cost #{cost}"
end
def audit_session_security
config = Rails.application.config
session_store = config.session_store
# Verify secure session configuration
if session_store == :cookie_store
options = config.session_options
raise "Sessions not secure" unless options[:secure] || !Rails.env.production?
raise "Sessions not httponly" unless options[:httponly]
raise "Session name too revealing" if options[:key] == '_myapp_session'
puts "✓ Session cookies are secure and httponly"
end
end
def audit_session_timeout
# Check for session timeout configuration
timeout_setting = Rails.application.config.session_timeout
if timeout_setting.nil?
puts "⚠ No session timeout configured"
elsif timeout_setting > 24.hours
puts "⚠ Session timeout too long: #{timeout_setting}"
else
puts "✓ Session timeout: #{timeout_setting}"
end
end
def run
puts "Auditing Authentication..."
audit_password_storage
audit_session_security
audit_session_timeout
puts "Authentication audit complete"
end
end
Auditing Authorization Logic
Authorization flaws grant access to resources without proper permission checks. Audits trace request paths and verify authorization occurs before data access.
# Test authorization enforcement
class AuthorizationAudit < Minitest::Test
def setup
@admin = users(:admin)
@user = users(:regular_user)
@other_user_document = documents(:other_users_private_doc)
end
def test_prevents_unauthorized_document_access
sign_in @user
get document_path(@other_user_document)
assert_response :forbidden, "User accessed unauthorized document"
end
def test_prevents_privilege_escalation_via_parameter
sign_in @user
# Attempt to escalate to admin via parameter tampering
patch user_path(@user), params: { user: { role: 'admin' } }
@user.reload
refute_equal 'admin', @user.role, "User escalated privileges"
end
def test_enforces_row_level_security
sign_in @user
# Verify scoping prevents access to other users' data
documents = Document.accessible_by(current_ability)
assert documents.all? { |doc| doc.user_id == @user.id },
"User accessed documents they don't own"
end
def test_api_endpoints_require_authorization
# Test API endpoints without authentication
get api_v1_users_path
assert_response :unauthorized, "API allows unauthenticated access"
# Test API with valid token but insufficient permissions
api_sign_in @user
get api_v1_admin_reports_path
assert_response :forbidden, "API allows unauthorized access"
end
end
Auditing Input Validation and Sanitization
Input validation prevents injection attacks and ensures data integrity. Audits check that user input is validated, sanitized, and safely used throughout the application.
# Audit input handling in controllers
class InputValidationAudit
def audit_sql_injection_risks
findings = []
# Find all where clauses with string interpolation
Dir.glob("app/models/**/*.rb").each do |file|
content = File.read(file)
content.scan(/where\s*\(["'].*#\{.*\}.*["']\)/) do |match|
findings << { file: file, issue: 'SQL injection risk', code: match }
end
content.scan(/find_by_sql\s*\(["'].*#\{.*\}.*["']\)/) do |match|
findings << { file: file, issue: 'SQL injection via find_by_sql', code: match }
end
end
findings
end
def audit_command_injection_risks
findings = []
Dir.glob("app/**/*.rb").each do |file|
content = File.read(file)
# Check for system() with string interpolation
content.scan(/system\s*\(["'].*#\{.*\}.*["']\)/) do |match|
findings << { file: file, issue: 'Command injection risk', code: match }
end
# Check for backticks with user input
content.scan(/`.*#\{.*\}.*`/) do |match|
findings << { file: file, issue: 'Command injection via backticks', code: match }
end
end
findings
end
def audit_xss_risks
findings = []
Dir.glob("app/views/**/*.erb").each do |file|
content = File.read(file)
# Find raw output or html_safe usage
content.scan(/<%==|raw\(|html_safe/) do |match|
findings << { file: file, issue: 'Potential XSS via unescaped output', code: match }
end
end
findings
end
def report
all_findings = audit_sql_injection_risks +
audit_command_injection_risks +
audit_xss_risks
if all_findings.empty?
puts "✓ No injection vulnerabilities detected"
else
puts "⚠ Found #{all_findings.count} potential injection vulnerabilities:"
all_findings.each do |finding|
puts "\n#{finding[:file]}"
puts " Issue: #{finding[:issue]}"
puts " Code: #{finding[:code]}"
end
end
end
end
Continuous Integration Security Pipeline
Automated security checks in CI/CD pipelines catch vulnerabilities before code reaches production. A comprehensive pipeline runs multiple tools and enforces security standards.
# Rakefile for CI security checks
namespace :security do
desc 'Run all security audits'
task all: [:brakeman, :bundle_audit, :rubocop_security] do
puts "\n✓ All security checks passed"
end
desc 'Run Brakeman security scanner'
task :brakeman do
puts "Running Brakeman security scan..."
system('brakeman --no-pager --confidence-level 2') ||
abort("Brakeman detected security issues")
end
desc 'Check for vulnerable dependencies'
task :bundle_audit do
puts "Checking for vulnerable dependencies..."
system('bundle audit update') || abort("Failed to update vulnerability database")
system('bundle audit check') || abort("Vulnerable dependencies detected")
end
desc 'Run RuboCop security cops'
task :rubocop_security do
puts "Running RuboCop security checks..."
system('rubocop --only Security') || abort("RuboCop security violations detected")
end
desc 'Generate security report'
task :report do
puts "Generating security report..."
system('brakeman -o tmp/brakeman-report.html')
system('bundle audit check --format json > tmp/bundle-audit-report.json')
puts "Reports generated in tmp/"
end
end
# CI configuration example (.github/workflows/security.yml format as Ruby comment)
# name: Security Audit
# on: [push, pull_request]
# jobs:
# security:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v2
# - uses: ruby/setup-ruby@v1
# with:
# bundler-cache: true
# - run: bundle exec rake security:all
Common Pitfalls
Security auditing introduces specific challenges that reduce effectiveness when not addressed properly. Understanding these pitfalls improves audit quality and prevents wasted effort.
False Positive Fatigue occurs when automated tools report numerous non-exploitable issues. Teams begin ignoring warnings after repeatedly investigating false positives. Brakeman may flag parameterized queries as SQL injection if it cannot track data flow completely. XSS warnings appear for admin-only content where the risk is minimal. Configure tools to reduce noise, maintain ignore lists with documented justifications, and periodically review ignored warnings to ensure they remain valid.
# Poor approach - ignoring entire vulnerability classes
brakeman --skip-checks=SQL,XSS # Misses real vulnerabilities
# Better approach - ignoring specific false positives with documentation
# brakeman.yml
:ignore_warnings:
- fingerprint: abc123def456
note: "Admin-only form, XSS risk accepted - admins are trusted"
approved_by: "security-team"
approved_date: "2025-01-15"
Over-Reliance on Automated Tools creates blind spots. Static analysis cannot detect business logic flaws, understand complex authentication schemes, or verify that authorization checks are comprehensive. A tool may confirm that password fields use bcrypt without noticing that password reset tokens are predictable. Manual code review remains essential for authorization logic, session management, and cryptographic implementations.
Ignoring Context treats all vulnerabilities as equally critical regardless of exposure. An SQL injection in an internal admin tool accessed only by database administrators differs from the same vulnerability in a public API endpoint. Audits must consider attack surface, data sensitivity, and existing compensating controls when prioritizing remediation.
Incomplete Remediation fixes the reported issue without addressing the underlying pattern. Developers fix one SQL injection vulnerability but miss five similar cases elsewhere in the codebase. Remediation should include pattern analysis to find all instances of the vulnerability, root cause analysis to prevent recurrence, and testing to verify the fix is complete.
# Incomplete fix - only addresses the reported line
def search_users(name)
User.where("name = ?", name) # Fixed parameterization
end
def search_by_email(email)
User.where("email = '#{email}'") # Same vulnerability still exists
end
# Complete fix - establishes secure pattern throughout codebase
module SecureQuery
def self.search_users(name:)
User.where("name = ?", name)
end
def self.search_by_email(email:)
User.where("email = ?", email)
end
end
Insufficient Regression Testing allows fixed vulnerabilities to reappear in later code changes. Security fixes need corresponding test cases that fail if the vulnerability is reintroduced. Tests should verify the vulnerability is exploitable before the fix, then confirm the fix prevents exploitation.
Audit Timing Issues delay vulnerability discovery until late in development or after deployment. Pre-commit hooks catch issues immediately with minimal remediation cost. Auditing only before releases allows vulnerabilities to accumulate and forces rushed fixes. Continuous auditing through automated pipelines and periodic manual review maintains security throughout development.
Credential Exposure in Audit Reports occurs when security scan output includes sensitive data. Brakeman reports may contain hardcoded API keys or passwords that it detected. Bundle audit output shows gem names and versions that reveal technology stack details. Audit reports require access controls appropriate to their sensitive content.
Missing Edge Case Analysis focuses audits on happy paths while ignoring error conditions. Authentication bypass vulnerabilities often hide in error handling paths where validation is skipped or exceptions bypass authorization. Audits must test error scenarios, invalid inputs, and boundary conditions.
# Vulnerability in error handling
def show
@document = Document.find(params[:id])
authorize @document
render json: @document
rescue ActiveRecord::RecordNotFound
render json: { error: 'Not found' }, status: 404
# Authorization was bypassed for non-existent IDs
# Information disclosure: confirms whether IDs exist
end
# Secure error handling
def show
@document = current_user.documents.find(params[:id]) # Authorization via scoping
render json: @document
rescue ActiveRecord::RecordNotFound
render json: { error: 'Not found' }, status: 404
end
Supply Chain Blindness focuses exclusively on application code while ignoring third-party dependencies. Vulnerable gems introduce exploitable issues. Malicious packages can exfiltrate data during bundle install. Dependency auditing must cover direct and transitive dependencies, verify package integrity, and monitor for newly disclosed vulnerabilities in deployed applications.
Inadequate Security Knowledge among developers conducting audits leads to missed vulnerabilities. Reviewers unfamiliar with timing attacks may not recognize timing side channels in authentication. Those lacking cryptography knowledge may approve weak encryption. Regular security training and involving security specialists in audits improves detection quality.
Reference
Security Audit Checklist
| Category | Check | Tool |
|---|---|---|
| Authentication | Passwords use bcrypt with cost ≥ 12 | Manual review |
| Authentication | Sessions expire after inactivity | Manual review |
| Authentication | Session tokens regenerate on login | Manual review |
| Authentication | Password reset tokens are single-use | Manual review |
| Authorization | Authorization checks before data access | Brakeman, Manual |
| Authorization | Direct object references validated | Manual review |
| Authorization | API endpoints enforce authorization | Manual review |
| Input Validation | SQL uses parameterized queries | Brakeman |
| Input Validation | System calls escape arguments | Brakeman, RuboCop |
| Input Validation | User input is validated | Manual review |
| Output Encoding | Templates escape output by default | Brakeman |
| Output Encoding | Raw output is justified | Manual review |
| CSRF | CSRF protection enabled | Brakeman |
| CSRF | State-changing operations require token | Manual review |
| Dependencies | No known vulnerable gems | bundler-audit |
| Dependencies | Dependencies are up to date | Manual review |
| Secrets | No hardcoded credentials | Brakeman, RuboCop |
| Secrets | Secrets loaded from environment | Manual review |
| Secrets | secret_key_base is unique | Manual review |
| Cryptography | Uses SecureRandom for tokens | RuboCop |
| Cryptography | Encryption uses authenticated modes | Manual review |
| Cryptography | TLS enforced in production | Manual review |
| Configuration | Debug features disabled in production | Manual review |
| Configuration | Verbose errors disabled in production | Manual review |
| Configuration | Security headers configured | Manual review |
| Logging | Sensitive parameters filtered | Manual review |
| Logging | Security events logged | Manual review |
Common Vulnerability Patterns
| Vulnerability | Insecure Pattern | Secure Pattern |
|---|---|---|
| SQL Injection | where("name = '#{name}'") | where("name = ?", name) |
| Command Injection | system("cmd #{input}") | system("cmd", input) |
| XSS | <%== user_content %> | <%= user_content %> |
| Mass Assignment | update(params[:user]) | update(user_params) |
| Insecure Direct Object Reference | find(params[:id]) | current_user.documents.find(params[:id]) |
| Hardcoded Secret | api_key = "abc123" | api_key = ENV['API_KEY'] |
| Weak Randomness | rand(1000000) | SecureRandom.random_number(1000000) |
| Insecure Deserialization | Marshal.load(data) | JSON.parse(data) |
| Path Traversal | File.read(params[:file]) | File.read(Rails.root.join('files', params[:file])) |
| Open Redirect | redirect_to params[:url] | redirect_to safe_url(params[:url]) |
Tool Comparison
| Tool | Type | Speed | Accuracy | Language Support | CI Integration |
|---|---|---|---|---|---|
| Brakeman | SAST | Fast | High | Rails-specific | Excellent |
| bundler-audit | Dependency | Very Fast | High | Ruby gems | Excellent |
| RuboCop Security | SAST | Fast | Medium | Ruby | Excellent |
| Snyk | Dependency + SAST | Medium | High | Multi-language | Excellent |
| OWASP Dependency-Check | Dependency | Slow | Medium | Multi-language | Good |
Security Headers Configuration
| Header | Purpose | Rails Configuration |
|---|---|---|
| Content-Security-Policy | Prevents XSS and data injection | config.content_security_policy |
| X-Frame-Options | Prevents clickjacking | Enabled by default |
| X-Content-Type-Options | Prevents MIME sniffing | Enabled by default |
| Strict-Transport-Security | Enforces HTTPS | config.force_ssl = true |
| X-XSS-Protection | Enables browser XSS filter | Enabled by default |
| Referrer-Policy | Controls referrer information | config.action_dispatch.default_headers |
Severity Rating Guide
| Severity | Characteristics | Example | Response Time |
|---|---|---|---|
| Critical | Remote code execution, authentication bypass | SQL injection in login, deserialization vulnerability | Immediate |
| High | Data breach, privilege escalation | Authorization bypass, mass assignment of role | 24-48 hours |
| Medium | Information disclosure, limited injection | Stack traces in production, XSS in admin panel | 1-2 weeks |
| Low | Configuration weakness, deprecated function | Missing security header, weak session timeout | Next release |
| Info | Best practice deviation, low-risk finding | Predictable session names, verbose error messages | Backlog |