CrackedRuby CrackedRuby

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