Overview
The OWASP Top 10 represents the most critical security risks to web applications, compiled and maintained by the Open Web Application Security Project. This industry-standard document serves as a foundational reference for developers, security professionals, and organizations to identify and mitigate common vulnerabilities. Updated every few years based on data from security firms and vulnerability databases, the list reflects the evolving threat landscape facing modern applications.
The current OWASP Top 10 (2021) identifies risks through a combination of contributed data from 40+ organizations covering over 500,000 applications and survey responses from security practitioners. Each risk category represents a class of vulnerabilities rather than individual flaws, providing a broader perspective on application security. The list emphasizes risks that combine high likelihood of exploitation with significant potential impact on confidentiality, integrity, and availability.
Understanding the OWASP Top 10 provides several critical benefits for development teams. First, it establishes a common vocabulary for discussing security concerns across technical and non-technical stakeholders. Second, it prioritizes security efforts by focusing on the most frequently exploited and impactful vulnerabilities. Third, it offers concrete examples and remediation strategies that teams can immediately apply to their applications. Organizations use the OWASP Top 10 as a baseline for security requirements, compliance frameworks, and security training programs.
The 2021 list introduced significant changes from previous versions, including new categories for Insecure Design and Software and Data Integrity Failures. These additions reflect the growing importance of security considerations during the design phase and the increased risks from supply chain attacks. The list also merged and renamed existing categories to better represent modern vulnerability patterns, such as consolidating injection flaws and expanding authentication concerns.
Key Principles
The OWASP Top 10 organizes security risks around three fundamental principles: identifying the vulnerability class, understanding the attack vector, and implementing appropriate countermeasures. Each risk category encompasses multiple related vulnerabilities that share common root causes and remediation strategies.
Broken Access Control occurs when applications fail to properly enforce permissions on authenticated users. Attackers exploit these flaws to access unauthorized functionality or data by manipulating URLs, API requests, or internal application state. This risk moved to the number one position in 2021 due to its prevalence across applications and the severity of potential data breaches. Access control failures include vertical privilege escalation (accessing admin functions), horizontal privilege escalation (accessing other users' data), and insecure direct object references where user-controlled parameters reference internal objects without authorization checks.
Cryptographic Failures encompasses vulnerabilities related to inadequate protection of sensitive data both at rest and in transit. Previously known as "Sensitive Data Exposure," this category emphasizes failures in cryptographic implementation rather than the outcome. Common issues include storing passwords in plaintext, using weak encryption algorithms, failing to encrypt sensitive data during transmission, and improper key management. The risk extends beyond traditional encryption to include secure random number generation and proper hashing functions for password storage.
Injection vulnerabilities occur when untrusted data gets interpreted as commands or queries by an interpreter. SQL injection remains the most well-known variant, but the category includes OS command injection, LDAP injection, expression language injection, and template injection. These vulnerabilities arise when applications concatenate user input directly into queries or commands without proper validation or parameterization. Successful injection attacks can result in data theft, data modification, denial of service, or complete system compromise.
Insecure Design represents a new category addressing security flaws at the architectural and design level. Unlike implementation bugs, these vulnerabilities stem from missing or ineffective security controls in the application design. Examples include business logic flaws, failure to perform threat modeling, and insufficient security requirements during planning phases. This category recognizes that secure coding practices alone cannot compensate for fundamental design weaknesses.
Security Misconfiguration includes a broad range of issues from incorrectly configured permissions to running applications with debugging enabled in production. Default configurations often prioritize ease of setup over security, leaving applications vulnerable if not properly hardened. This category encompasses missing security patches, unnecessary features enabled, default accounts with unchanged passwords, overly detailed error messages revealing system information, and misconfigured security headers.
Vulnerable and Outdated Components addresses the risks of using third-party libraries, frameworks, and other software dependencies with known vulnerabilities. Modern applications typically include dozens or hundreds of dependencies, creating a large attack surface. Organizations often lack visibility into their component inventory, fail to monitor for vulnerabilities, or delay applying patches. Attackers actively scan for vulnerable components using automated tools, making this a high-probability risk.
Identification and Authentication Failures covers weaknesses in user authentication, session management, and credential storage. Flaws in this category allow attackers to compromise passwords, keys, session tokens, or exploit other authentication weaknesses to assume user identities. Common issues include weak password policies, credential stuffing attacks, predictable session identifiers, improper session timeout, and missing multi-factor authentication for sensitive operations.
Software and Data Integrity Failures represents another new category focusing on code and infrastructure that fails to protect against integrity violations. This includes applications that rely on updates from untrusted sources, insecure deserialization of untrusted data, and CI/CD pipelines without integrity verification. Supply chain attacks targeting build processes and deployment pipelines fall into this category, reflecting the increasing sophistication of modern attacks.
Security Logging and Monitoring Failures addresses insufficient logging, detection, and response capabilities. Without proper logging and monitoring, breaches remain undetected for extended periods, allowing attackers to pivot to additional systems, maintain persistence, and extract data. This category includes logging insufficient events, storing logs insecurely, generating unclear log messages, missing real-time alerting, and lacking incident response procedures.
Server-Side Request Forgery (SSRF) occurs when applications fetch remote resources based on user-supplied URLs without validating the destination. Attackers exploit SSRF to access internal systems, cloud metadata services, or perform port scanning of internal networks. This vulnerability has increased in prevalence with cloud computing and microservices architectures, where applications frequently interact with internal APIs and services.
Ruby Implementation
Ruby and Rails provide multiple security features and conventions that help prevent OWASP Top 10 vulnerabilities when used correctly. The framework includes protection mechanisms enabled by default, but developers must understand how to properly implement and extend these protections.
For Broken Access Control, Rails provides authorization frameworks and conventions but does not enforce access control by default. The strong parameters feature prevents mass assignment vulnerabilities, while authorization gems like Pundit and CanCanCan offer declarative policy enforcement.
class ArticlesController < ApplicationController
before_action :authenticate_user!
before_action :authorize_article, only: [:edit, :update, :destroy]
def update
if @article.update(article_params)
redirect_to @article
else
render :edit
end
end
private
def authorize_article
@article = Article.find(params[:id])
unless @article.user_id == current_user.id || current_user.admin?
redirect_to root_path, alert: "Not authorized"
end
end
def article_params
params.require(:article).permit(:title, :content, :published)
end
end
Using Pundit for more structured authorization:
class ArticlePolicy < ApplicationPolicy
def update?
user.admin? || record.user_id == user.id
end
def destroy?
user.admin? || record.user_id == user.id
end
end
class ArticlesController < ApplicationController
def update
@article = Article.find(params[:id])
authorize @article
if @article.update(article_params)
redirect_to @article
else
render :edit
end
end
end
For Cryptographic Failures, Rails includes ActiveSupport::MessageEncryptor and has_secure_password for proper credential handling. The framework uses bcrypt for password hashing with appropriate work factors.
class User < ApplicationRecord
has_secure_password
# Validates password length and confirmation automatically
validates :password, length: { minimum: 12 }, if: :password_required?
encrypts :ssn, deterministic: true
encrypts :medical_history
def password_required?
password.present? || password_confirmation.present?
end
end
Encrypting sensitive data at rest using Rails 7 encryption:
# config/initializers/encryption.rb
Rails.application.credentials.config
# Encrypting specific attributes
class Payment < ApplicationRecord
encrypts :credit_card_number
encrypts :cvv, deterministic: false
blind_index :credit_card_number
end
# Encrypting entire database columns
class Message < ApplicationRecord
encrypts :content, key: :encryption_key_for_messages
private
def encryption_key_for_messages
Rails.application.credentials.message_encryption_key
end
end
Injection prevention in Rails relies primarily on parameterized queries through ActiveRecord. The ORM escapes values automatically when using the query interface correctly, but developers must avoid string concatenation in queries.
# Vulnerable to SQL injection
def search_vulnerable
query = params[:search]
User.where("name = '#{query}'") # NEVER DO THIS
end
# Safe parameterized query
def search_safe
query = params[:search]
User.where("name = ?", query)
end
# Safe using hash conditions
def search_hash
User.where(name: params[:search])
end
# Safe with Arel for complex queries
def search_arel
users = User.arel_table
User.where(users[:name].matches("%#{sanitize_sql_like(params[:search])}%"))
end
For OS command injection prevention:
# Vulnerable to command injection
def process_file_vulnerable
filename = params[:file]
`convert #{filename} output.pdf` # NEVER DO THIS
end
# Safe using array syntax
def process_file_safe
filename = params[:file]
system("convert", filename, "output.pdf")
end
# Safe with explicit argument passing
def resize_image_safe
width = params[:width].to_i
height = params[:height].to_i
filename = params[:file]
# Validate numeric parameters
return unless width.positive? && height.positive?
# Pass arguments separately
system("convert", filename, "-resize", "#{width}x#{height}", "output.jpg")
end
Security Misconfiguration prevention involves proper configuration management and environment-specific settings:
# config/environments/production.rb
Rails.application.configure do
# Disable detailed error pages
config.consider_all_requests_local = false
# Enable HTTPS
config.force_ssl = true
# Set secure cookie flags
config.session_store :cookie_store,
key: '_app_session',
secure: true,
httponly: true,
same_site: :lax
# Configure security headers
config.action_dispatch.default_headers = {
'X-Frame-Options' => 'SAMEORIGIN',
'X-Content-Type-Options' => 'nosniff',
'X-XSS-Protection' => '1; mode=block',
'Referrer-Policy' => 'strict-origin-when-cross-origin'
}
end
Vulnerable Components management requires regular dependency updates and vulnerability scanning:
# Gemfile - specify version constraints
gem 'rails', '~> 7.0.4'
gem 'puma', '~> 6.0'
gem 'devise', '~> 4.9'
# Use bundler-audit to check for vulnerabilities
# Run: bundle audit check --update
Authentication Failures prevention using Devise with proper configuration:
# config/initializers/devise.rb
Devise.setup do |config|
# Use strong password encryption
config.stretches = Rails.env.test? ? 1 : 12
# Lock account after failed attempts
config.lock_strategy = :failed_attempts
config.maximum_attempts = 5
config.unlock_strategy = :time
config.unlock_in = 1.hour
# Expire sessions
config.timeout_in = 30.minutes
config.expire_auth_token_on_timeout = true
# Password complexity
config.password_length = 12..128
end
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
:lockable, :timeoutable, :trackable
validates :password, password_strength: true
end
Security Logging implementation:
class ApplicationController < ActionController::Base
before_action :log_security_event
private
def log_security_event
Rails.logger.info({
event: 'request',
user_id: current_user&.id,
ip: request.remote_ip,
method: request.method,
path: request.fullpath,
user_agent: request.user_agent,
timestamp: Time.current.iso8601
}.to_json)
end
def log_authentication_failure(user_email)
Rails.logger.warn({
event: 'authentication_failure',
email: user_email,
ip: request.remote_ip,
timestamp: Time.current.iso8601
}.to_json)
end
end
Practical Examples
Broken Access Control - Insecure Direct Object Reference
# Vulnerable implementation
class DocumentsController < ApplicationController
def show
@document = Document.find(params[:id]) # No authorization check
end
end
# Secure implementation
class DocumentsController < ApplicationController
def show
@document = current_user.documents.find(params[:id])
# Raises ActiveRecord::RecordNotFound if document doesn't belong to user
rescue ActiveRecord::RecordNotFound
redirect_to root_path, alert: "Document not found"
end
end
# Alternative with explicit authorization
class DocumentsController < ApplicationController
def show
@document = Document.find(params[:id])
authorize @document # Using Pundit
end
end
class DocumentPolicy < ApplicationPolicy
def show?
record.user_id == user.id ||
record.shared_with?(user) ||
user.admin?
end
end
Cryptographic Failures - Weak Password Storage
# Vulnerable - custom weak hashing
class User < ApplicationRecord
before_save :hash_password
def hash_password
self.password_digest = Digest::MD5.hexdigest(password) # WEAK
end
def authenticate(password)
password_digest == Digest::MD5.hexdigest(password)
end
end
# Secure - using bcrypt via has_secure_password
class User < ApplicationRecord
has_secure_password
validates :password,
length: { minimum: 12 },
format: {
with: /\A(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])/,
message: "must include uppercase, lowercase, number and special character"
}
end
# Usage
user = User.create(email: 'user@example.com', password: 'SecureP@ssw0rd123')
user.authenticate('SecureP@ssw0rd123') # Returns user if correct, false otherwise
Injection - SQL Injection
# Vulnerable - string concatenation
class ProductsController < ApplicationController
def search
category = params[:category]
@products = Product.where("category = '#{category}'") # SQL INJECTION
end
end
# Attack: params[:category] = "'; DROP TABLE products; --"
# Resulting query: SELECT * FROM products WHERE category = ''; DROP TABLE products; --'
# Secure - parameterized query
class ProductsController < ApplicationController
def search
@products = Product.where("category = ?", params[:category])
end
end
# Secure - hash conditions
class ProductsController < ApplicationController
def search
conditions = {}
conditions[:category] = params[:category] if params[:category].present?
conditions[:min_price] = params[:min_price] if params[:min_price].present?
@products = Product.where(conditions)
end
end
Insecure Design - Missing Rate Limiting
# Vulnerable - no rate limiting on sensitive endpoint
class PasswordResetsController < ApplicationController
def create
user = User.find_by(email: params[:email])
user&.send_password_reset_email
redirect_to root_path, notice: "Check your email"
end
end
# Secure - implementing rate limiting
class PasswordResetsController < ApplicationController
before_action :check_rate_limit, only: [:create]
def create
user = User.find_by(email: params[:email])
if user
increment_reset_attempts
user.send_password_reset_email
end
# Always show same message to prevent user enumeration
redirect_to root_path, notice: "If that email exists, you'll receive reset instructions"
end
private
def check_rate_limit
key = "password_reset:#{request.remote_ip}"
attempts = Rails.cache.read(key) || 0
if attempts >= 5
render json: { error: "Too many attempts" }, status: :too_many_requests
return
end
end
def increment_reset_attempts
key = "password_reset:#{request.remote_ip}"
Rails.cache.increment(key, 1, expires_in: 1.hour)
end
end
SSRF - Unvalidated URL Fetching
# Vulnerable - fetching arbitrary URLs
class WebhooksController < ApplicationController
def test
url = params[:callback_url]
response = Net::HTTP.get(URI(url)) # SSRF vulnerability
render json: { response: response }
end
end
# Attack: params[:callback_url] = "http://169.254.169.254/latest/meta-data/"
# Accesses AWS metadata service, exposing credentials
# Secure - URL validation and allowlisting
class WebhooksController < ApplicationController
ALLOWED_DOMAINS = ['api.partner.com', 'webhooks.partner.com'].freeze
BLOCKED_IPS = ['127.0.0.1', '0.0.0.0', '169.254.169.254'].freeze
def test
url = params[:callback_url]
unless valid_url?(url)
render json: { error: "Invalid URL" }, status: :bad_request
return
end
response = fetch_url(url)
render json: { response: response }
end
private
def valid_url?(url)
uri = URI.parse(url)
# Check scheme
return false unless ['http', 'https'].include?(uri.scheme)
# Check domain allowlist
return false unless ALLOWED_DOMAINS.include?(uri.host)
# Resolve and check IP
resolved_ip = Resolv.getaddress(uri.host)
return false if BLOCKED_IPS.include?(resolved_ip)
return false if private_ip?(resolved_ip)
true
rescue URI::InvalidURIError, Resolv::ResolvError
false
end
def private_ip?(ip)
addr = IPAddr.new(ip)
addr.private? || addr.loopback? || addr.link_local?
end
def fetch_url(url)
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 5, read_timeout: 5) do |http|
request = Net::HTTP::Get.new(uri)
response = http.request(request)
response.body
end
end
end
Common Pitfalls
Relying Solely on Client-Side Validation
Developers often implement validation in JavaScript without server-side enforcement. Attackers bypass client-side checks by directly manipulating HTTP requests.
# Insufficient - only client-side validation
# views/users/_form.html.erb
<%= form_with(model: @user) do |f| %>
<%= f.text_field :role, maxlength: 20, pattern: "[a-z]+" %>
<% end %>
# Controller lacks validation
class UsersController < ApplicationController
def create
@user = User.create(user_params) # Accepts any role value
end
end
# Correct - server-side validation
class User < ApplicationRecord
VALID_ROLES = ['user', 'moderator'].freeze
validates :role, inclusion: { in: VALID_ROLES }
validate :prevent_admin_assignment
private
def prevent_admin_assignment
if role == 'admin' && !created_by_admin?
errors.add(:role, "cannot be admin")
end
end
end
Trusting User Input in Authorization Checks
Using user-supplied parameters for authorization decisions without verification:
# Vulnerable - trusting user_id parameter
class AccountsController < ApplicationController
def show
if params[:user_id].to_i == current_user.id # Easily manipulated
@account = Account.find(params[:id])
else
redirect_to root_path
end
end
end
# Correct - verifying ownership through database relationship
class AccountsController < ApplicationController
def show
@account = current_user.accounts.find(params[:id])
end
end
Logging Sensitive Information
Including passwords, tokens, or personally identifiable information in logs:
# Vulnerable - logging sensitive data
class SessionsController < ApplicationController
def create
Rails.logger.info("Login attempt: #{params.inspect}") # Logs password!
user = User.find_by(email: params[:email])
if user&.authenticate(params[:password])
session[:user_id] = user.id
end
end
end
# Correct - filtering sensitive parameters
# config/initializers/filter_parameter_logging.rb
Rails.application.config.filter_parameters += [
:password, :password_confirmation, :token, :api_key,
:credit_card, :ssn, :secret
]
class SessionsController < ApplicationController
def create
Rails.logger.info("Login attempt for: #{params[:email]}") # Safe
user = User.find_by(email: params[:email])
if user&.authenticate(params[:password])
session[:user_id] = user.id
Rails.logger.info("Successful login: user_id=#{user.id}")
else
Rails.logger.warn("Failed login: email=#{params[:email]} ip=#{request.remote_ip}")
end
end
end
Insecure Deserialization
Using YAML or Marshal to deserialize untrusted data enables remote code execution:
# Vulnerable - deserializing user input
class PreferencesController < ApplicationController
def update
preferences = Marshal.load(Base64.decode64(params[:data])) # RCE vulnerability
current_user.update(preferences: preferences)
end
end
# Correct - using JSON with explicit parsing
class PreferencesController < ApplicationController
def update
preferences = JSON.parse(params[:data])
# Explicitly allow only expected keys
allowed = preferences.slice('theme', 'language', 'timezone')
current_user.update(preferences: allowed)
rescue JSON::ParserError
render json: { error: "Invalid data" }, status: :bad_request
end
end
Mass Assignment Vulnerabilities
Failing to restrict which attributes can be updated through user input:
# Vulnerable - no parameter filtering
class UsersController < ApplicationController
def update
@user = User.find(params[:id])
@user.update(params[:user]) # All attributes can be modified
end
end
# Attack: Send { user: { admin: true } } to gain admin privileges
# Correct - using strong parameters
class UsersController < ApplicationController
def update
@user = User.find(params[:id])
@user.update(user_params)
end
private
def user_params
params.require(:user).permit(:name, :email, :bio)
# admin attribute not permitted
end
end
Session Fixation Vulnerabilities
Failing to regenerate session IDs after authentication:
# Vulnerable - reusing existing session
class SessionsController < ApplicationController
def create
user = User.find_by(email: params[:email])
if user&.authenticate(params[:password])
session[:user_id] = user.id # Session ID not regenerated
end
end
end
# Correct - regenerating session after login
class SessionsController < ApplicationController
def create
user = User.find_by(email: params[:email])
if user&.authenticate(params[:password])
reset_session # Destroys old session, creates new one
session[:user_id] = user.id
end
end
end
Tools & Ecosystem
Brakeman provides static analysis security scanning for Rails applications, detecting common vulnerabilities including SQL injection, XSS, and insecure configurations.
# Gemfile
group :development do
gem 'brakeman', require: false
end
# Run scan
# bundle exec brakeman
# Configuration file: config/brakeman.yml
:skip_files:
- "lib/tasks/**/*.rake"
:ignore_warnings:
- fingerprint: "abc123"
note: "False positive - input sanitized elsewhere"
bundler-audit scans Gemfile.lock for gems with known security vulnerabilities:
# Gemfile
group :development do
gem 'bundler-audit', require: false
end
# Check for vulnerabilities
# bundle exec bundle-audit check --update
# CI integration
# .github/workflows/security.yml
- name: Security audit
run: |
gem install bundler-audit
bundle-audit check --update
Rack::Attack provides rate limiting and request throttling to prevent abuse:
# Gemfile
gem 'rack-attack'
# config/initializers/rack_attack.rb
class Rack::Attack
# Throttle login attempts
throttle('logins/ip', limit: 5, period: 20.seconds) do |req|
if req.path == '/login' && req.post?
req.ip
end
end
# Throttle API requests
throttle('api/ip', limit: 300, period: 5.minutes) do |req|
req.ip if req.path.start_with?('/api/')
end
# Block suspicious requests
blocklist('bad_actors') do |req|
Blocklist.include?(req.ip)
end
end
SecureHeaders gem configures HTTP security headers:
# Gemfile
gem 'secure_headers'
# config/initializers/secure_headers.rb
SecureHeaders::Configuration.default do |config|
config.x_frame_options = "DENY"
config.x_content_type_options = "nosniff"
config.x_xss_protection = "1; mode=block"
config.x_download_options = "noopen"
config.x_permitted_cross_domain_policies = "none"
config.referrer_policy = %w[origin-when-cross-origin strict-origin-when-cross-origin]
config.csp = {
default_src: %w['self'],
script_src: %w['self' 'unsafe-inline'],
style_src: %w['self' 'unsafe-inline'],
img_src: %w['self' data: https:],
connect_src: %w['self'],
font_src: %w['self'],
object_src: %w['none'],
frame_ancestors: %w['none']
}
end
Devise provides comprehensive authentication with security features:
# Gemfile
gem 'devise'
gem 'devise-security' # Additional security extensions
# config/initializers/devise.rb
Devise.setup do |config|
config.password_length = 12..128
config.lock_strategy = :failed_attempts
config.maximum_attempts = 5
config.unlock_in = 1.hour
config.timeout_in = 30.minutes
end
# Add password expiry and history
class User < ApplicationRecord
devise :database_authenticatable, :lockable, :timeoutable,
:password_expirable, :password_archivable
# Using devise-security extensions
password_history_count = 5
password_expiration_days = 90
end
Pundit provides policy-based authorization:
# Gemfile
gem 'pundit'
# app/policies/application_policy.rb
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
@user = user
@record = record
end
def index?
false
end
def show?
false
end
def create?
false
end
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
include Pundit::Authorization
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
private
def user_not_authorized
flash[:alert] = "Access denied"
redirect_to(request.referrer || root_path)
end
end
Reference
OWASP Top 10 2021 Quick Reference
| Risk | Category | Impact | Mitigation Priority |
|---|---|---|---|
| A01 | Broken Access Control | High | Critical |
| A02 | Cryptographic Failures | High | Critical |
| A03 | Injection | High | Critical |
| A04 | Insecure Design | Medium-High | High |
| A05 | Security Misconfiguration | Medium | High |
| A06 | Vulnerable Components | Medium-High | High |
| A07 | Authentication Failures | High | Critical |
| A08 | Data Integrity Failures | Medium-High | High |
| A09 | Logging/Monitoring Failures | Medium | Medium |
| A10 | SSRF | Medium | Medium-High |
Rails Security Configuration Checklist
| Configuration | Location | Secure Setting |
|---|---|---|
| Force SSL | production.rb | config.force_ssl = true |
| Session timeout | devise.rb | config.timeout_in = 30.minutes |
| CSRF protection | application_controller.rb | protect_from_forgery with: :exception |
| SQL injection | queries | Use parameterized queries or ActiveRecord |
| Mass assignment | controllers | Use strong_parameters |
| Sensitive logs | filter_parameter_logging.rb | Filter passwords, tokens, keys |
| Cookie security | session_store.rb | secure: true, httponly: true |
| Error details | production.rb | consider_all_requests_local = false |
| Default headers | application.rb | Configure X-Frame-Options, CSP |
Common Vulnerability Patterns
| Vulnerability | Insecure Pattern | Secure Alternative |
|---|---|---|
| SQL Injection | String concatenation in queries | Parameterized queries with ? or named params |
| XSS | Raw HTML output | Use Rails HTML escaping (default) |
| CSRF | Missing authenticity token | Rails CSRF protection (default) |
| Command Injection | Backticks with user input | system() with separate arguments |
| Path Traversal | Direct file access from params | Allowlist filenames, validate paths |
| Open Redirect | Redirect to params URL | Validate URL against allowlist |
| Insecure Randomness | rand() for tokens | SecureRandom methods |
| Weak Crypto | MD5, SHA1 for passwords | bcrypt via has_secure_password |
Security Testing Commands
| Tool | Command | Purpose |
|---|---|---|
| Brakeman | bundle exec brakeman -A | Full security scan with all checks |
| bundler-audit | bundle exec bundle-audit check --update | Check gem vulnerabilities |
| Rails Best Practices | bundle exec rails_best_practices | Code quality and security review |
| RuboCop Security | bundle exec rubocop -r rubocop-rails | Static code analysis |
HTTP Security Headers
| Header | Recommended Value | Purpose |
|---|---|---|
| X-Frame-Options | DENY or SAMEORIGIN | Prevent clickjacking |
| X-Content-Type-Options | nosniff | Prevent MIME sniffing |
| X-XSS-Protection | 1; mode=block | Enable XSS filter |
| Content-Security-Policy | Strict policy | Control resource loading |
| Strict-Transport-Security | max-age=31536000 | Force HTTPS |
| Referrer-Policy | strict-origin-when-cross-origin | Control referrer information |
Secure Coding Guidelines
| Practice | Implementation | Rails Helper |
|---|---|---|
| Input Validation | Validate all user input | ActiveModel validations |
| Output Encoding | Escape HTML, JS, SQL | ERB escaping (default), sanitize helper |
| Authentication | Strong password requirements | has_secure_password, Devise |
| Authorization | Check permissions on every request | Pundit, CanCanCan |
| Session Management | Secure cookies, timeout | Rails session config, Devise |
| Cryptography | Strong algorithms, proper key management | ActiveSupport encryption |
| Error Handling | Generic error messages | Custom error pages |
| Logging | Log security events, filter sensitive data | Rails logger, filter_parameters |