CrackedRuby CrackedRuby

Overview

Database security protects stored data from malicious attacks, unauthorized access, and accidental exposure. Modern applications handle sensitive information including user credentials, financial records, personal data, and business intelligence. A single security breach can expose millions of records, resulting in financial losses, legal liability, and reputational damage.

Database security operates at multiple layers. Network security controls which systems can reach the database server. Authentication verifies user identity before granting access. Authorization determines which operations each authenticated user can perform. Input validation prevents malicious data from entering the system. Encryption protects data both in transit and at rest. Audit logging records all database activities for security analysis and compliance.

The threat landscape includes SQL injection attacks, which manipulate queries to access unauthorized data or execute malicious commands. Credential theft exposes database accounts to attackers. Privilege escalation allows limited users to gain administrative access. Data exfiltration extracts sensitive information without authorization. Insider threats involve authorized users misusing their access. Denial of service attacks overwhelm database resources to disrupt availability.

Ruby applications connect to databases through libraries like pg (PostgreSQL), mysql2 (MySQL), and sqlite3 (SQLite), or through Object-Relational Mapping frameworks like ActiveRecord and Sequel. These tools provide security features including parameterized queries, connection encryption, and credential management. Applications must configure and use these features correctly to maintain security.

Database security intersects with application security, network security, and compliance requirements. Applications act as the primary interface to database systems. Secure coding practices in application code prevent most database security vulnerabilities. Network configurations control traffic between application servers and database servers. Compliance frameworks like PCI DSS, HIPAA, and GDPR mandate specific security controls for protected data types.

Key Principles

Authentication and Authorization form the foundation of database access control. Authentication verifies the identity of users or applications attempting to connect. Authorization determines which database objects and operations each authenticated identity can access. Strong authentication requires complex passwords, key-based authentication, or certificate-based authentication rather than default or weak credentials.

Database management systems implement role-based access control (RBAC) to manage permissions. Roles group related permissions and assign them to users or applications. A read-only role grants SELECT privileges without INSERT, UPDATE, or DELETE capabilities. An application role provides only the minimum permissions needed for the application to function. A DBA role grants full administrative access for maintenance tasks.

Principle of Least Privilege mandates granting each user or application the minimum permissions required to perform legitimate tasks. Application database accounts should not have administrative privileges, DROP TABLE permissions, or access to tables they don't use. Limiting privileges reduces the potential damage from compromised accounts, SQL injection attacks, or application bugs.

Defense in Depth applies multiple layers of security controls rather than relying on a single mechanism. Even if attackers bypass one control, additional layers prevent full compromise. Defense in depth combines network firewalls, application authentication, database authentication, query parameterization, input validation, encryption, and monitoring.

Input Validation and Parameterization prevent injection attacks by treating user input as data rather than executable code. SQL injection occurs when applications concatenate user input directly into SQL statements. Attackers craft input containing SQL syntax that alters query behavior. Parameterized queries separate SQL logic from data values. The database treats parameter values as literal data regardless of content.

Encryption protects data confidentiality both in transit and at rest. Transport Layer Security (TLS) encrypts network connections between applications and databases, preventing eavesdropping and man-in-the-middle attacks. At-rest encryption protects data files, backups, and storage media from unauthorized access if physical security fails. Column-level encryption protects specific sensitive fields like credit card numbers or social security numbers.

Connection Security ensures database connections use encrypted channels and authenticate both endpoints. Applications should connect using TLS/SSL to encrypt all data transmitted over the network. Certificate validation confirms the database server identity and prevents impersonation. Connection strings must not contain plaintext credentials in source code or configuration files.

Audit Logging and Monitoring record database activities for security analysis, compliance, and incident response. Audit logs capture authentication attempts, privilege changes, data modifications, and query executions. Monitoring detects suspicious patterns like failed login attempts, unusual query volumes, or access to sensitive tables outside normal hours. Security Information and Event Management (SIEM) systems aggregate and analyze logs from multiple sources.

Credential Management handles database passwords and connection details securely. Applications should never hard-code credentials in source code. Environment variables, encrypted configuration files, or dedicated secret management systems provide better alternatives. Credentials should rotate regularly. Development, staging, and production environments must use different credentials.

Backup Security protects backup files from unauthorized access while ensuring data recovery capability. Encrypted backups prevent data exposure if backup media is lost or stolen. Access controls limit who can restore backups. Backup testing verifies both data integrity and restoration procedures. Off-site backup storage protects against site-wide disasters.

Ruby Implementation

Ruby database libraries implement security features that applications must activate and configure correctly. The pg gem for PostgreSQL, mysql2 for MySQL, and sqlite3 for SQLite provide low-level database access with built-in parameterization support. ActiveRecord and Sequel build higher-level abstractions with additional security features.

Parameterized Queries with ActiveRecord use placeholder syntax to separate SQL logic from data values. ActiveRecord automatically parameterizes queries when using its query interface:

# Secure: ActiveRecord parameterizes the value
User.where("email = ?", user_input)
# Generates: SELECT * FROM users WHERE email = $1
# Parameters: ["user@example.com"]

# Secure: Hash conditions are parameterized
User.where(email: user_input)

# Secure: Named placeholders
User.where("email = :email AND status = :status", 
           email: user_input, status: "active")

Dangerous String Interpolation creates SQL injection vulnerabilities. Never concatenate user input directly into SQL strings:

# VULNERABLE: String interpolation allows SQL injection
User.where("email = '#{user_input}'")
# If user_input = "' OR '1'='1"
# Generates: SELECT * FROM users WHERE email = '' OR '1'='1'
# Returns all users

# VULNERABLE: String concatenation
query = "SELECT * FROM users WHERE name = '" + params[:name] + "'"
ActiveRecord::Base.connection.execute(query)

Raw SQL with Parameterization handles cases requiring custom SQL. Use bind parameters even when writing raw queries:

# Secure: Bind parameters in raw SQL
sql = "SELECT * FROM users WHERE created_at > ? AND role = ?"
User.find_by_sql([sql, 30.days.ago, "admin"])

# Secure: Named parameters in raw SQL
sql = "SELECT * FROM users WHERE email LIKE :pattern"
User.find_by_sql([sql, {pattern: "%@example.com"}])

Sequel Security provides similar parameterization with slightly different syntax:

# Secure: Sequel placeholder syntax
DB[:users].where("email = ?", user_input)

# Secure: Hash conditions
DB[:users].where(email: user_input)

# Secure: Named placeholders
DB[:users].where("status = :status", status: user_input)

# Secure: Filter method with hash
DB[:users].filter(email: user_input, active: true)

Connection Encryption Configuration specifies TLS/SSL settings in database configuration files. For PostgreSQL with the pg gem:

# config/database.yml
production:
  adapter: postgresql
  host: db.example.com
  database: myapp_production
  sslmode: require
  sslrootcert: /path/to/ca-certificate.crt
  sslcert: /path/to/client-certificate.crt
  sslkey: /path/to/client-key.key

The sslmode parameter controls encryption requirements. The value require enforces encryption but doesn't verify server identity. The value verify-ca verifies the server certificate against a certificate authority. The value verify-full additionally verifies the server hostname matches the certificate.

Environment-Based Credential Management keeps sensitive data out of source control:

# config/database.yml
production:
  adapter: postgresql
  host: <%= ENV['DATABASE_HOST'] %>
  database: <%= ENV['DATABASE_NAME'] %>
  username: <%= ENV['DATABASE_USERNAME'] %>
  password: <%= ENV['DATABASE_PASSWORD'] %>
  sslmode: require

# .env file (not committed to version control)
DATABASE_HOST=db.production.com
DATABASE_NAME=myapp_production
DATABASE_USERNAME=myapp_user
DATABASE_PASSWORD=complex_secure_password_here

Encrypted Credentials in Rails provides built-in credential management:

# Edit encrypted credentials
$ EDITOR=vim rails credentials:edit

# config/credentials.yml.enc (encrypted)
database:
  password: secret_password_here
  
secret_key_base: long_random_string

# Access in application code
Rails.application.credentials.database[:password]

ActiveRecord Encryption protects sensitive attributes at the application level:

class User < ApplicationRecord
  encrypts :ssn, deterministic: false
  encrypts :email, deterministic: true
  encrypts :credit_card, deterministic: false
end

# Encrypted values stored in database
user = User.create(ssn: "123-45-6789")
# Database contains encrypted blob, not plaintext

# Decryption automatic on read
user.ssn # => "123-45-6789"

# Deterministic encryption allows querying
User.where(email: "user@example.com") # Works
User.where(ssn: "123-45-6789") # Doesn't work (non-deterministic)

Mass Assignment Protection prevents attackers from modifying unauthorized attributes:

class User < ApplicationRecord
  # Strong parameters in controller
  def user_params
    params.require(:user).permit(:email, :name)
    # Excludes :admin, :role, and other sensitive fields
  end
end

# Controller action
def create
  @user = User.new(user_params)
  # Attacker cannot set admin=true via HTTP parameters
end

SQL Sanitization Methods provide additional protection when needed:

# Sanitize single value
sanitized = ActiveRecord::Base.sanitize_sql(["name = ?", user_input])

# Sanitize array of values
values = ["value1", "value2", "value3"]
sanitized = ActiveRecord::Base.sanitize_sql_array(
  ["name IN (?)", values]
)

# Quote identifier (table/column name)
table = ActiveRecord::Base.connection.quote_table_name(user_table)
column = ActiveRecord::Base.connection.quote_column_name(user_column)

Practical Examples

SQL Injection Vulnerability and Fix demonstrates the most common database security flaw:

# VULNERABLE: User search feature with SQL injection
class UsersController < ApplicationController
  def search
    @users = User.where("name LIKE '%#{params[:query]}%'")
  end
end

# Attack scenario:
# User enters: '; DROP TABLE users; --
# Resulting SQL: SELECT * FROM users WHERE name LIKE '%'; DROP TABLE users; --%'
# Executes: DROP TABLE users

# SECURE: Parameterized query prevents injection
class UsersController < ApplicationController
  def search
    @users = User.where("name LIKE ?", "%#{params[:query]}%")
  end
end

# Same attack input treated as literal string
# SQL: SELECT * FROM users WHERE name LIKE $1
# Parameters: ["%'; DROP TABLE users; --%"]
# Returns: No matching users

Authentication with Multiple Security Layers implements defense in depth:

class DatabaseConnection
  def self.establish
    config = {
      adapter: 'postgresql',
      host: ENV['DB_HOST'],
      port: ENV['DB_PORT'] || 5432,
      database: ENV['DB_NAME'],
      username: ENV['DB_USERNAME'],
      password: ENV.fetch('DB_PASSWORD'),
      
      # Require TLS encryption
      sslmode: 'verify-full',
      sslrootcert: '/etc/ssl/certs/ca-certificate.crt',
      
      # Connection pool limits
      pool: 5,
      timeout: 5000,
      
      # Enable prepared statements
      prepared_statements: true,
      
      # Statement timeout prevents long-running queries
      variables: {
        statement_timeout: 30000 # 30 seconds
      }
    }
    
    ActiveRecord::Base.establish_connection(config)
  rescue KeyError => e
    raise "Database configuration incomplete: #{e.message}"
  end
end

# Verify connection security
connection = ActiveRecord::Base.connection
ssl_status = connection.execute("SHOW ssl").first
raise "SSL not enabled" unless ssl_status['ssl'] == 'on'

Secure Password Storage and Authentication never stores passwords in plaintext:

class User < ApplicationRecord
  # bcrypt automatically salts and hashes passwords
  has_secure_password
  
  # Password complexity requirements
  validates :password, 
    length: { minimum: 12 },
    format: { 
      with: /(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])/,
      message: "must include uppercase, lowercase, number, and symbol"
    },
    if: :password_digest_changed?
    
  # Prevent timing attacks on email lookup
  def self.find_by_email_constant_time(email)
    users = User.where(email: email.downcase).limit(2).to_a
    users.first if users.size == 1
  end
end

# Authentication controller
class SessionsController < ApplicationController
  def create
    # Constant-time lookup prevents user enumeration
    user = User.find_by_email_constant_time(params[:email])
    
    # Constant-time comparison prevents timing attacks
    if user&.authenticate(params[:password])
      # Rate limiting prevents brute force
      if RateLimiter.allowed?(user.id)
        session[:user_id] = user.id
        redirect_to dashboard_path
      else
        render json: { error: "Too many attempts" }, status: 429
      end
    else
      # Same response time whether user exists or not
      sleep 0.1
      render json: { error: "Invalid credentials" }, status: 401
    end
  end
end

Role-Based Access Control restricts data access by user role:

class User < ApplicationRecord
  enum role: { viewer: 0, editor: 1, admin: 2 }
  
  def can_modify?(record)
    case role
    when 'viewer'
      false
    when 'editor'
      record.user_id == id
    when 'admin'
      true
    end
  end
end

class RecordsController < ApplicationController
  before_action :require_authentication
  
  def update
    @record = Record.find(params[:id])
    
    unless current_user.can_modify?(@record)
      render json: { error: "Unauthorized" }, status: 403
      return
    end
    
    if @record.update(record_params)
      render json: @record
    else
      render json: @record.errors, status: 422
    end
  end
  
  private
  
  def record_params
    # Filter allowed attributes based on role
    allowed = [:title, :content]
    allowed << :published if current_user.admin?
    params.require(:record).permit(allowed)
  end
end

Sensitive Data Encryption protects confidential information:

class PaymentMethod < ApplicationRecord
  # Non-deterministic encryption prevents querying
  encrypts :card_number
  encrypts :cvv
  
  # Deterministic encryption allows lookups
  encrypts :card_fingerprint, deterministic: true
  
  # Store only last 4 digits in plaintext for display
  before_validation :extract_last_four
  
  def self.find_by_fingerprint(card_number)
    fingerprint = Digest::SHA256.hexdigest(card_number)
    find_by(card_fingerprint: fingerprint)
  end
  
  private
  
  def extract_last_four
    return unless card_number
    self.last_four = card_number.to_s[-4..]
  end
end

# Usage
payment = PaymentMethod.create(
  card_number: "4532123456789012"
)

# Database contains encrypted card_number
# Only last_four stored in plaintext: "9012"

# Retrieve by fingerprint
found = PaymentMethod.find_by_fingerprint("4532123456789012")
found.card_number # Decrypts automatically

Audit Logging for Compliance records all data access:

class AuditLog < ApplicationRecord
  belongs_to :user
  
  enum action: {
    read: 0, create: 1, update: 2, delete: 3,
    login: 4, logout: 5, failed_login: 6
  }
end

module Auditable
  extend ActiveSupport::Concern
  
  included do
    after_create :log_create
    after_update :log_update
    after_destroy :log_destroy
  end
  
  private
  
  def log_create
    create_audit_log(:create, changes: saved_changes)
  end
  
  def log_update
    create_audit_log(:update, changes: saved_changes)
  end
  
  def log_destroy
    create_audit_log(:delete, changes: attributes)
  end
  
  def create_audit_log(action, changes:)
    AuditLog.create(
      user: Current.user,
      auditable: self,
      action: action,
      changes: changes,
      ip_address: Current.ip_address,
      user_agent: Current.user_agent
    )
  end
end

# Include in sensitive models
class FinancialRecord < ApplicationRecord
  include Auditable
end

# Query audit trail
def audit_trail_for(user)
  AuditLog.where(user: user)
    .order(created_at: :desc)
    .includes(:auditable)
end

Common Pitfalls

String Interpolation in Queries remains the most frequent security mistake. Developers interpolate variables into SQL strings for convenience, creating injection vulnerabilities:

# VULNERABLE: Direct interpolation
def find_users_by_status(status)
  User.where("status = '#{status}'")
end

# Attack: status = "' OR '1'='1"
# Returns all users regardless of status

# SECURE: Always use placeholders
def find_users_by_status(status)
  User.where("status = ?", status)
end

Dynamic Column Names pose special challenges. Column and table names cannot use parameterization. Applications accepting user input for column names must whitelist valid values:

# VULNERABLE: User-controlled column name
def sort_users(sort_column)
  User.order(sort_column)
end

# Attack: sort_column = "email; DROP TABLE users; --"

# SECURE: Whitelist valid column names
ALLOWED_SORT_COLUMNS = ['email', 'created_at', 'name'].freeze

def sort_users(sort_column)
  column = ALLOWED_SORT_COLUMNS.include?(sort_column) ? 
           sort_column : 'created_at'
  User.order(column)
end

# Alternative: Use symbols which are safe
def sort_users(sort_column)
  User.order(sort_column.to_sym) if User.column_names.include?(sort_column)
end

Exposed Connection Strings occur when credentials appear in committed code, error messages, or logs:

# WRONG: Credentials in code
ActiveRecord::Base.establish_connection(
  adapter: 'postgresql',
  host: 'db.example.com',
  username: 'myapp',
  password: 'secretpassword123' # Committed to git!
)

# WRONG: Connection string in logs
Rails.logger.info("Connected to #{ActiveRecord::Base.connection_config}")
# Logs contain password

# CORRECT: Environment variables
ActiveRecord::Base.establish_connection(ENV['DATABASE_URL'])

# CORRECT: Filtered logging
Rails.application.config.filter_parameters += [
  :password, :database_url, :connection_string
]

Over-Privileged Database Accounts violate the principle of least privilege. Application accounts with excessive permissions amplify attack impact:

# WRONG: Application uses DBA account
# Database user has ALL PRIVILEGES
# SQL injection can DROP DATABASE

# CORRECT: Limited application privileges
# CREATE USER myapp_user WITH PASSWORD 'secure_password';
# GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES 
#   IN SCHEMA public TO myapp_user;
# REVOKE CREATE, DROP, TRUNCATE ON DATABASE FROM myapp_user;

# Verify privileges in application startup
def verify_database_privileges
  conn = ActiveRecord::Base.connection
  
  # Check we cannot drop tables
  begin
    conn.execute("DROP TABLE nonexistent_table")
  rescue ActiveRecord::StatementInvalid => e
    unless e.message.include?("permission denied")
      raise "Database account has excessive DROP privileges"
    end
  end
end

Missing Connection Encryption transmits sensitive data in plaintext across networks:

# WRONG: No SSL configuration
# Database traffic unencrypted
production:
  adapter: postgresql
  host: db.example.com
  database: myapp_production

# CORRECT: Require SSL/TLS
production:
  adapter: postgresql
  host: db.example.com
  database: myapp_production
  sslmode: verify-full
  sslrootcert: /etc/ssl/certs/ca-bundle.crt
  
# Verify encryption at runtime
def verify_connection_security
  result = ActiveRecord::Base.connection.execute(
    "SELECT ssl_is_used()"
  ).first
  
  raise "Unencrypted database connection" unless result['ssl_is_used']
end

Timing Attacks on Authentication leak information through response timing variations:

# VULNERABLE: Different timing for valid/invalid users
def authenticate(email, password)
  user = User.find_by(email: email)
  return false unless user # Fast response if user doesn't exist
  
  user.authenticate(password) # Slow bcrypt comparison if user exists
end

# Attacker can enumerate valid email addresses by timing

# SECURE: Constant-time comparison
def authenticate(email, password)
  user = User.find_by(email: email)
  
  if user
    result = user.authenticate(password)
  else
    # Perform dummy bcrypt comparison to maintain constant time
    BCrypt::Password.create("dummy_password")
    result = false
  end
  
  result
end

Insufficient Validation Before Database Operations allows attackers to bypass security controls:

# VULNERABLE: No validation of referenced records
def transfer_funds(from_account_id, to_account_id, amount)
  from = Account.find(from_account_id)
  to = Account.find(to_account_id)
  
  from.balance -= amount
  to.balance += amount
  
  from.save!
  to.save!
end

# Attack: User references account they don't own

# SECURE: Verify ownership
def transfer_funds(from_account_id, to_account_id, amount)
  from = current_user.accounts.find(from_account_id)
  to = Account.find(to_account_id)
  
  raise "Insufficient funds" if from.balance < amount
  raise "Invalid amount" unless amount.positive?
  
  Account.transaction do
    from.update!(balance: from.balance - amount)
    to.update!(balance: to.balance + amount)
    
    TransferLog.create!(
      from_account: from,
      to_account: to,
      amount: amount,
      user: current_user
    )
  end
end

Testing Approaches

SQL Injection Testing verifies query parameterization prevents injection attacks:

require 'rails_helper'

RSpec.describe UsersController, type: :controller do
  describe "GET #search" do
    it "prevents SQL injection in search query" do
      # Attempt SQL injection
      malicious_input = "'; DROP TABLE users; --"
      
      get :search, params: { query: malicious_input }
      
      # Table still exists
      expect { User.count }.not_to raise_error
      
      # No users matched the literal string
      expect(assigns(:users)).to be_empty
    end
    
    it "treats special characters as literal values" do
      User.create!(name: "'; SELECT * FROM users; --")
      
      get :search, params: { query: "'; SELECT" }
      
      expect(assigns(:users).count).to eq(1)
    end
  end
end

Authorization Testing confirms access control enforcement:

RSpec.describe RecordsController, type: :controller do
  let(:admin) { create(:user, role: :admin) }
  let(:editor) { create(:user, role: :editor) }
  let(:viewer) { create(:user, role: :viewer) }
  let(:record) { create(:record, user: editor) }
  
  describe "PUT #update" do
    it "allows admin to update any record" do
      sign_in admin
      put :update, params: { 
        id: record.id, 
        record: { title: "Updated" } 
      }
      expect(response).to have_http_status(:success)
    end
    
    it "allows editor to update their own records" do
      sign_in editor
      put :update, params: { 
        id: record.id, 
        record: { title: "Updated" } 
      }
      expect(response).to have_http_status(:success)
    end
    
    it "prevents editor from updating others' records" do
      other_editor = create(:user, role: :editor)
      sign_in other_editor
      
      put :update, params: { 
        id: record.id, 
        record: { title: "Updated" } 
      }
      
      expect(response).to have_http_status(:forbidden)
      expect(record.reload.title).not_to eq("Updated")
    end
    
    it "prevents viewer from updating any record" do
      sign_in viewer
      put :update, params: { 
        id: record.id, 
        record: { title: "Updated" } 
      }
      expect(response).to have_http_status(:forbidden)
    end
  end
end

Encryption Testing validates data protection mechanisms:

RSpec.describe PaymentMethod, type: :model do
  describe "encryption" do
    it "encrypts card number in database" do
      payment = PaymentMethod.create!(
        card_number: "4532123456789012"
      )
      
      # Check encrypted value in database
      raw_value = ActiveRecord::Base.connection.execute(
        "SELECT card_number FROM payment_methods WHERE id = #{payment.id}"
      ).first['card_number']
      
      expect(raw_value).not_to eq("4532123456789012")
      expect(raw_value).not_to include("4532")
    end
    
    it "decrypts card number on read" do
      payment = PaymentMethod.create!(
        card_number: "4532123456789012"
      )
      
      reloaded = PaymentMethod.find(payment.id)
      expect(reloaded.card_number).to eq("4532123456789012")
    end
    
    it "allows deterministic encryption for lookups" do
      payment = PaymentMethod.create!(
        card_number: "4532123456789012"
      )
      
      found = PaymentMethod.find_by_fingerprint("4532123456789012")
      expect(found).to eq(payment)
    end
  end
end

Connection Security Testing verifies encryption configuration:

RSpec.describe "Database connection", type: :system do
  it "uses SSL/TLS encryption" do
    result = ActiveRecord::Base.connection.execute(
      "SHOW ssl"
    ).first
    
    expect(result['ssl']).to eq('on')
  end
  
  it "verifies server certificate" do
    config = ActiveRecord::Base.connection_config
    
    expect(config[:sslmode]).to eq('verify-full')
    expect(config[:sslrootcert]).to be_present
  end
  
  it "uses prepared statements" do
    config = ActiveRecord::Base.connection_config
    expect(config[:prepared_statements]).to be true
  end
end

Audit Log Testing ensures compliance with logging requirements:

RSpec.describe "Audit logging", type: :system do
  let(:user) { create(:user) }
  
  before do
    sign_in user
    Current.user = user
  end
  
  it "logs record creation" do
    expect {
      FinancialRecord.create!(
        amount: 1000,
        description: "Payment"
      )
    }.to change { AuditLog.count }.by(1)
    
    log = AuditLog.last
    expect(log.action).to eq("create")
    expect(log.user).to eq(user)
    expect(log.changes).to include("amount" => [nil, 1000])
  end
  
  it "logs record updates" do
    record = create(:financial_record, amount: 1000)
    
    expect {
      record.update!(amount: 2000)
    }.to change { AuditLog.count }.by(1)
    
    log = AuditLog.last
    expect(log.action).to eq("update")
    expect(log.changes).to include("amount" => [1000, 2000])
  end
  
  it "includes IP address and user agent" do
    record = create(:financial_record)
    
    Current.ip_address = "192.168.1.100"
    Current.user_agent = "Mozilla/5.0"
    
    record.update!(amount: 2000)
    
    log = AuditLog.last
    expect(log.ip_address).to eq("192.168.1.100")
    expect(log.user_agent).to eq("Mozilla/5.0")
  end
end

Security Scanner Integration automates vulnerability detection:

# spec/security/brakeman_spec.rb
require 'rails_helper'
require 'brakeman'

RSpec.describe "Security scanning" do
  it "passes Brakeman security checks" do
    tracker = Brakeman.run(
      app_path: Rails.root,
      print_report: true,
      pager: false
    )
    
    expect(tracker.filtered_warnings).to be_empty,
      "Brakeman found security issues:\n" +
      tracker.filtered_warnings.map(&:message).join("\n")
  end
end

# spec/security/bundler_audit_spec.rb
RSpec.describe "Dependency security" do
  it "has no known vulnerable dependencies" do
    output = `bundle audit check --update`
    
    expect($?.exitstatus).to eq(0),
      "Vulnerable dependencies detected:\n#{output}"
  end
end

Tools & Ecosystem

Brakeman performs static analysis to detect security vulnerabilities in Rails applications:

# Install
gem 'brakeman', group: :development

# Run scan
$ brakeman --ensure-latest

# Generate report
$ brakeman -o report.html

# Configuration file: config/brakeman.yml
ignore_file: config/brakeman.ignore
check_paths:
  - app/controllers
  - app/models
  - app/views
sql_injection_confidence_threshold: low

Bundler Audit checks Ruby gems against vulnerability databases:

# Install
gem 'bundler-audit', group: :development

# Check for vulnerable dependencies
$ bundle audit check --update

# Continuous monitoring
$ bundle audit check

# Integrate in CI pipeline
# .github/workflows/security.yml
- name: Security audit
  run: |
    bundle install
    bundle audit check --update

Database Security Gems provide additional protection layers:

# Strong migrations - prevent dangerous migrations
gem 'strong_migrations'

# Detects n+1 queries and unused eager loading
gem 'bullet', group: :development

# Database query performance monitoring
gem 'rack-mini-profiler', group: :development

# SQL query analysis
gem 'pg_query'

# Database credential encryption
gem 'lockbox'
gem 'blind_index' # For searchable encryption

PostgreSQL Security Extensions enhance database-level protection:

# Enable pgcrypto for encryption functions
CREATE EXTENSION pgcrypto;

# Enable pg_stat_statements for query monitoring
CREATE EXTENSION pg_stat_statements;

# Row-level security policies
CREATE POLICY user_isolation ON documents
  USING (user_id = current_setting('app.current_user_id')::int);

ALTER TABLE documents ENABLE ROW LEVEL SECURITY;

MySQL Security Configuration hardens database server settings:

# /etc/mysql/my.cnf
[mysqld]
# Require SSL connections
require_secure_transport = ON

# Disable LOCAL INFILE to prevent file reading
local_infile = 0

# Set statement timeout
max_execution_time = 30000

# Enable audit logging
plugin-load-add = audit_log.so
audit_log_file = /var/log/mysql/audit.log

# Restrict network binding
bind-address = 127.0.0.1

Database Activity Monitoring tracks and alerts on suspicious behavior:

# Custom monitoring with Scout APM
gem 'scout_apm'

# config/scout_apm.yml
production:
  monitor: true
  key: <%= ENV['SCOUT_KEY'] %>
  log_level: warn
  
  # Monitor slow queries
  database_query_monitoring: true
  slow_query_threshold: 500

# Application Performance Monitoring
gem 'skylight'

# SQL query monitoring
gem 'active_record_query_trace', group: :development

Secret Management Tools secure credential storage and rotation:

# AWS Secrets Manager
gem 'aws-sdk-secretsmanager'

class SecretManager
  def self.database_credentials
    client = Aws::SecretsManager::Client.new(
      region: ENV['AWS_REGION']
    )
    
    secret = client.get_secret_value(
      secret_id: 'production/database'
    )
    
    JSON.parse(secret.secret_string)
  end
end

# HashiCorp Vault
gem 'vault'

Vault.configure do |config|
  config.address = ENV['VAULT_ADDR']
  config.token = ENV['VAULT_TOKEN']
  config.ssl_verify = true
end

credentials = Vault.logical.read('secret/data/database')

Intrusion Detection Systems identify attack patterns:

# Fail2ban configuration for database attacks
# /etc/fail2ban/jail.local
[postgresql]
enabled = true
port = 5432
filter = postgresql
logpath = /var/log/postgresql/postgresql-*.log
maxretry = 3
bantime = 3600

# Rack::Attack for application-level rate limiting
gem 'rack-attack'

# config/initializers/rack_attack.rb
Rack::Attack.throttle('req/ip', limit: 300, period: 5.minutes) do |req|
  req.ip
end

Rack::Attack.throttle('login/ip', limit: 5, period: 20.seconds) do |req|
  req.ip if req.path == '/login' && req.post?
end

Reference

SQL Injection Prevention

Method Example Use Case
Placeholder where("email = ?", value) Single parameter
Array placeholder where("id IN (?)", ids) Multiple values
Named placeholder where("email = :email", email: value) Multiple parameters
Hash conditions where(email: value) Equality conditions
Sanitize method sanitize_sql_array Custom SQL strings

ActiveRecord Security Methods

Method Purpose Returns
sanitize_sql Sanitize SQL string String
sanitize_sql_array Sanitize with parameters String
quote_table_name Escape table name String
quote_column_name Escape column name String
quote_string Escape string value String

PostgreSQL SSL Modes

Mode Encryption Certificate Verification Host Verification
disable No No No
allow Optional No No
prefer Preferred No No
require Yes No No
verify-ca Yes Yes No
verify-full Yes Yes Yes

Database Privilege Levels

Privilege Operations Recommended For
SELECT Read data Read-only accounts
INSERT Add records Application writes
UPDATE Modify records Application updates
DELETE Remove records Application deletes
TRUNCATE Empty tables Maintenance scripts
REFERENCES Foreign keys Schema management
TRIGGER Create triggers Advanced features
CREATE New objects Migration accounts
CONNECT Database access All accounts
TEMPORARY Temp tables Session data
EXECUTE Call functions Stored procedures
USAGE Schema access All accounts

Encryption Configuration

Setting Purpose Example
sslmode Connection encryption verify-full
sslrootcert CA certificate /etc/ssl/certs/ca.crt
sslcert Client certificate /etc/ssl/certs/client.crt
sslkey Client key /etc/ssl/private/client.key
sslpassword Key password encrypted_password

Security Headers for Database Responses

Header Purpose Value
X-Content-Type-Options Prevent MIME sniffing nosniff
X-Frame-Options Prevent clickjacking DENY
X-XSS-Protection XSS filter 1; mode=block
Strict-Transport-Security Force HTTPS max-age=31536000
Content-Security-Policy Resource restrictions default-src 'self'

Audit Log Event Types

Event Description Required Data
Authentication Login attempts User, IP, success/failure
Authorization Permission checks User, resource, granted/denied
Data Access Record reads User, table, record ID
Data Modification Create/update/delete User, changes, timestamp
Schema Changes DDL operations User, operation, object
Permission Changes Grant/revoke User, target, privileges
Configuration Changes Settings updates User, setting, old/new value

Security Testing Checklist

Test Type What to Verify Tools
SQL Injection Parameterization works Manual testing, sqlmap
Authentication Password strength RSpec, custom tests
Authorization Access controls RSpec, Pundit tests
Encryption Data encrypted Database inspection
SSL/TLS Connection encrypted OpenSSL, Wireshark
Logging Events recorded Log analysis
Dependencies No vulnerabilities bundler-audit
Static Analysis Code security Brakeman

Common Attack Patterns

Attack Indicator Prevention
SQL Injection Special chars in input Parameterization
Authentication Bypass Multiple failed logins Rate limiting
Privilege Escalation Unauthorized operations Authorization checks
Data Exfiltration Large query results Result size limits
Timing Attack Response time analysis Constant-time operations
Mass Assignment Unexpected attributes Strong parameters
CSRF Missing token CSRF protection

Database Connection Pool Settings

Setting Purpose Recommended Value
pool Max connections 5 per process
timeout Connection wait time 5000 ms
checkout_timeout Connection checkout 5 seconds
reaping_frequency Idle connection cleanup 60 seconds
idle_timeout Max idle time 300 seconds
prepared_statements Statement caching true

Emergency Response Procedures

Scenario Immediate Action Follow-up
SQL Injection Detected Block affected queries Audit all queries
Credential Leak Rotate passwords Review access logs
Unauthorized Access Revoke permissions Investigate source
Data Breach Isolate system Forensic analysis
DoS Attack Rate limit requests Identify attacker
Privilege Escalation Remove elevated access Audit permissions