Overview
Input validation examines data received from external sources to verify it conforms to expected criteria before the application processes or stores it. This defense mechanism prevents malicious or malformed data from compromising system integrity, corrupting data stores, or exposing security vulnerabilities.
Applications accept input from multiple sources: HTTP request parameters, form submissions, API payloads, file uploads, command-line arguments, environment variables, and database queries. Each input vector presents potential attack surfaces where adversaries inject malicious payloads, exploit parsing vulnerabilities, or trigger unintended application behavior.
Validation operates at the application boundary, acting as the first line of defense against injection attacks, data corruption, and business logic violations. Without proper validation, applications remain vulnerable to SQL injection, cross-site scripting (XSS), command injection, path traversal, and numerous other attack vectors that exploit unchecked input.
The validation process encompasses multiple dimensions: syntactic validation verifies data format and structure, semantic validation ensures data makes sense in context, type validation confirms data types match expectations, and business rule validation enforces domain-specific constraints. Each validation layer addresses different aspects of data integrity and security.
# Unvalidated input leading to SQL injection vulnerability
def find_user(username)
User.find_by_sql("SELECT * FROM users WHERE username = '#{username}'")
end
# Validated and parameterized query
def find_user(username)
return nil unless username.is_a?(String) && username.match?(/\A[a-zA-Z0-9_]+\z/)
User.where(username: username).first
end
Modern web frameworks provide validation infrastructure, but developers must understand validation principles to apply them correctly. Framework abstractions hide complexity but can create false security confidence when misapplied or incompletely implemented.
Key Principles
Input validation follows the principle of explicit rejection: define what constitutes valid input and reject everything else. This whitelist approach proves more secure than blacklist-based rejection, which attempts to enumerate all possible invalid inputs and inevitably misses edge cases or novel attack vectors.
Validation occurs as early as possible in the data processing pipeline, ideally at the point where external data enters the application. Early validation prevents malicious data from propagating through the system, reducing the attack surface and simplifying security analysis. Each layer of the application stack can apply additional validation appropriate to its abstraction level.
Type coercion and validation remain distinct operations. Type coercion converts data from one type to another, potentially masking invalid input by forcing it into an expected format. Validation explicitly checks whether data meets requirements without automatic transformation. Performing validation before coercion maintains data integrity and prevents subtle security issues.
# Dangerous: coercion masks validation issues
age = params[:age].to_i # "malicious" becomes 0
process_age(age) if age > 0 # Passes but may not reflect user intent
# Correct: validate before coercion
age_string = params[:age]
if age_string.match?(/\A\d+\z/)
age = age_string.to_i
process_age(age) if age > 0
else
raise ValidationError, "Age must be a positive integer"
end
Context determines validation requirements. The same data may require different validation depending on how it will be used. A string validated for display differs from one validated for use in system commands or database queries. Understanding the data's destination and processing informs appropriate validation rules.
Validation failures require explicit handling rather than silent acceptance or default value substitution. Applications should reject invalid input with clear error messages that inform users without exposing implementation details attackers could exploit. Error messages balance usability with security, providing enough information for legitimate users while avoiding information disclosure.
Defense in depth applies to validation. Multiple validation layers at different architectural boundaries provide redundancy against validation bypass. Client-side validation improves user experience but never substitutes for server-side validation, as clients remain under attacker control and can bypass any client-side checks.
Canonical form validation addresses encoding variations and normalization attacks. Attackers exploit different encodings, Unicode normalization forms, or character representations to bypass validation. Converting input to a single canonical form before validation prevents these bypass techniques.
Security Implications
Input validation forms the foundation of application security, directly preventing the most prevalent web application vulnerabilities. SQL injection attacks exploit applications that concatenate unsanitized input into database queries, allowing attackers to execute arbitrary SQL commands. Proper validation combined with parameterized queries eliminates this entire vulnerability class.
Cross-site scripting (XSS) occurs when applications include unvalidated user input in HTML output without proper encoding. Attackers inject JavaScript code that executes in victim browsers, stealing credentials, hijacking sessions, or performing actions on behalf of users. Input validation restricts input to safe character sets and formats, while output encoding handles display concerns.
Command injection vulnerabilities arise when applications pass unvalidated input to system shell commands. Attackers inject command separators and additional commands, gaining unauthorized system access or executing malicious code. Strict validation combined with avoiding shell invocation when possible mitigates this risk.
Path traversal attacks manipulate file paths to access files outside intended directories. Attackers use sequences like ../ to navigate directory structures, potentially accessing sensitive configuration files, source code, or system files. Validating file paths against allowed patterns and canonicalizing paths before use prevents traversal attacks.
# Vulnerable to path traversal
def read_user_file(filename)
File.read("uploads/#{filename}")
end
# Protected with validation
def read_user_file(filename)
# Reject path separators and parent directory references
raise SecurityError if filename.include?('/') || filename.include?('\\')
raise SecurityError if filename.include?('..')
# Whitelist allowed characters
raise SecurityError unless filename.match?(/\A[a-zA-Z0-9._-]+\z/)
path = File.expand_path(File.join('uploads', filename))
raise SecurityError unless path.start_with?(File.expand_path('uploads'))
File.read(path)
end
XML external entity (XXE) attacks exploit XML parsers that process external entity references in user-supplied XML. Attackers reference external files or network resources, potentially reading sensitive files or triggering denial-of-service conditions. Disabling external entity processing and validating XML structure prevents XXE attacks.
Buffer overflow vulnerabilities in languages with manual memory management occur when applications fail to validate input length, writing beyond allocated buffer boundaries. While Ruby's memory safety prevents traditional buffer overflows, validation still prevents resource exhaustion from excessively large inputs.
Business logic vulnerabilities result from insufficient semantic validation. Attackers manipulate prices, quantities, or other business parameters to exploit logic flaws. Validation must enforce not just data types and formats but also business rules and constraints specific to the application domain.
Mass assignment vulnerabilities occur in frameworks that automatically bind request parameters to object attributes. Attackers add unexpected parameters to modify protected attributes. Validation through explicit parameter filtering prevents unauthorized attribute modification.
Regular expression denial of service (ReDoS) exploits catastrophic backtracking in poorly designed regex patterns. Attackers provide input that causes exponential processing time, creating denial-of-service conditions. Careful regex design and input length limits mitigate ReDoS risks.
Ruby Implementation
Ruby provides multiple approaches to input validation, ranging from manual checks to framework-integrated validation systems. The standard library includes pattern matching, type checking, and string validation methods that form the foundation of custom validation logic.
Regular expressions validate string formats and patterns. Ruby's String#match? method performs pattern matching without creating match objects, improving performance for validation use cases. Character class shortcuts and anchors ensure complete string matching rather than partial matches.
# Email format validation
EMAIL_REGEX = /\A[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\z/
def valid_email?(email)
email.is_a?(String) && email.match?(EMAIL_REGEX)
end
# Phone number validation
PHONE_REGEX = /\A\+?[1-9]\d{1,14}\z/
def valid_phone?(phone)
phone.is_a?(String) && phone.match?(PHONE_REGEX)
end
# Alphanumeric with length constraints
def valid_username?(username)
username.is_a?(String) &&
username.match?(/\A[a-zA-Z0-9_]{3,20}\z/)
end
Type validation uses Ruby's type system and introspection capabilities. The is_a? method checks class hierarchy membership, while kind_of? provides an alias for the same functionality. Duck typing validation checks for method presence using respond_to?.
def process_number(value)
unless value.is_a?(Numeric)
raise TypeError, "Expected Numeric, got #{value.class}"
end
unless value.positive?
raise ArgumentError, "Value must be positive"
end
value * 2
end
# Duck typing validation
def process_enumerable(collection)
unless collection.respond_to?(:each)
raise TypeError, "Object must be enumerable"
end
collection.map { |item| item.to_s.upcase }
end
Rails Active Record validations provide declarative validation rules at the model layer. These validations execute before save operations, preventing invalid data from persisting to the database. Validation helpers cover common patterns like presence, format, uniqueness, and numericality.
class User < ApplicationRecord
validates :email, presence: true,
format: { with: URI::MailTo::EMAIL_REGEXP },
uniqueness: { case_sensitive: false }
validates :age, numericality: { only_integer: true,
greater_than_or_equal_to: 0,
less_than_or_equal_to: 150 }
validates :username, format: { with: /\A[a-zA-Z0-9_]+\z/ },
length: { minimum: 3, maximum: 20 },
uniqueness: true
validate :password_complexity
private
def password_complexity
return if password.blank?
unless password.match?(/[A-Z]/)
errors.add(:password, "must contain uppercase letter")
end
unless password.match?(/[a-z]/)
errors.add(:password, "must contain lowercase letter")
end
unless password.match?(/[0-9]/)
errors.add(:password, "must contain digit")
end
end
end
Strong parameters in Rails controllers prevent mass assignment vulnerabilities by requiring explicit parameter whitelisting. Controllers define permitted parameters, and the framework raises exceptions when unpermitted parameters are accessed.
class UsersController < ApplicationController
def create
@user = User.new(user_params)
if @user.save
redirect_to @user
else
render :new
end
end
private
def user_params
params.require(:user).permit(:username, :email, :age)
end
end
Custom validators encapsulate complex validation logic for reuse across models. Validators inherit from ActiveModel::Validator or ActiveModel::EachValidator depending on whether they validate entire models or individual attributes.
class EmailDomainValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return if value.blank?
domain = value.split('@').last
allowed_domains = options[:domains]
unless allowed_domains.include?(domain)
record.errors.add(attribute,
"must be from allowed domains: #{allowed_domains.join(', ')}")
end
end
end
class User < ApplicationRecord
validates :email, email_domain: { domains: ['company.com', 'partner.com'] }
end
The dry-validation gem provides a functional approach to validation with composable rules and detailed error messages. Contracts define schemas with type coercion, predicates, and custom rules.
require 'dry-validation'
UserContract = Dry::Validation.Contract do
params do
required(:username).filled(:string)
required(:email).filled(:string)
required(:age).filled(:integer)
end
rule(:email) do
unless /\A[^@\s]+@[^@\s]+\z/.match?(value)
key.failure('must be valid email format')
end
end
rule(:age) do
key.failure('must be non-negative') if value < 0
key.failure('must be realistic') if value > 150
end
end
# Usage
result = UserContract.call(username: 'john', email: 'invalid', age: -5)
result.success? # => false
result.errors.to_h # => {:email=>["must be valid email format"], :age=>["must be non-negative"]}
Practical Examples
User registration forms require comprehensive validation across multiple fields with different requirements. Email validation checks format, username validation enforces character restrictions and length limits, and password validation ensures sufficient complexity.
class RegistrationValidator
EMAIL_REGEX = /\A[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\z/
USERNAME_REGEX = /\A[a-zA-Z0-9_]+\z/
def self.validate(params)
errors = {}
# Email validation
email = params[:email].to_s.strip
if email.empty?
errors[:email] = "Email is required"
elsif !email.match?(EMAIL_REGEX)
errors[:email] = "Email format is invalid"
elsif email.length > 255
errors[:email] = "Email is too long"
end
# Username validation
username = params[:username].to_s.strip
if username.empty?
errors[:username] = "Username is required"
elsif !username.match?(USERNAME_REGEX)
errors[:username] = "Username can only contain letters, numbers, and underscores"
elsif username.length < 3
errors[:username] = "Username must be at least 3 characters"
elsif username.length > 20
errors[:username] = "Username cannot exceed 20 characters"
end
# Password validation
password = params[:password].to_s
if password.empty?
errors[:password] = "Password is required"
elsif password.length < 8
errors[:password] = "Password must be at least 8 characters"
elsif !password.match?(/[A-Z]/)
errors[:password] = "Password must contain an uppercase letter"
elsif !password.match?(/[a-z]/)
errors[:password] = "Password must contain a lowercase letter"
elsif !password.match?(/[0-9]/)
errors[:password] = "Password must contain a digit"
elsif !password.match?(/[^a-zA-Z0-9]/)
errors[:password] = "Password must contain a special character"
end
# Password confirmation
if params[:password] != params[:password_confirmation]
errors[:password_confirmation] = "Passwords do not match"
end
errors
end
end
# Controller usage
def create
errors = RegistrationValidator.validate(params)
if errors.empty?
user = User.create!(params.permit(:email, :username, :password))
redirect_to user
else
@errors = errors
render :new
end
end
API request validation handles JSON payloads with nested structures, arrays, and optional fields. Validation must handle missing keys, type mismatches, and constraint violations while providing detailed error information.
class ApiRequestValidator
def self.validate_create_order(payload)
errors = []
# Required top-level fields
unless payload.is_a?(Hash)
return ["Request body must be a JSON object"]
end
unless payload['customer_id'].is_a?(Integer)
errors << "customer_id must be an integer"
end
unless payload['items'].is_a?(Array)
errors << "items must be an array"
else
# Validate each item
payload['items'].each_with_index do |item, index|
unless item.is_a?(Hash)
errors << "items[#{index}] must be an object"
next
end
unless item['product_id'].is_a?(Integer)
errors << "items[#{index}].product_id must be an integer"
end
unless item['quantity'].is_a?(Integer) && item['quantity'] > 0
errors << "items[#{index}].quantity must be a positive integer"
end
if item['price']
unless item['price'].is_a?(Numeric) && item['price'] >= 0
errors << "items[#{index}].price must be a non-negative number"
end
end
end
end
# Optional shipping address
if payload['shipping_address']
address = payload['shipping_address']
['street', 'city', 'country'].each do |field|
unless address[field].is_a?(String) && !address[field].empty?
errors << "shipping_address.#{field} is required"
end
end
if address['postal_code']
unless address['postal_code'].match?(/\A[A-Z0-9\s-]{3,10}\z/i)
errors << "shipping_address.postal_code format is invalid"
end
end
end
errors
end
end
# API controller
def create_order
errors = ApiRequestValidator.validate_create_order(request_payload)
if errors.empty?
order = Order.create_from_api(request_payload)
render json: { order: order }, status: :created
else
render json: { errors: errors }, status: :unprocessable_entity
end
end
File upload validation prevents security vulnerabilities from malicious files. Validation checks file size, content type, filename safety, and file content to ensure uploaded files meet security requirements.
class FileUploadValidator
MAX_SIZE = 10.megabytes
ALLOWED_CONTENT_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf']
SAFE_FILENAME_REGEX = /\A[a-zA-Z0-9._-]+\z/
def self.validate(uploaded_file)
errors = []
unless uploaded_file.respond_to?(:read)
return ["Invalid file upload"]
end
# Size validation
if uploaded_file.size > MAX_SIZE
errors << "File size exceeds maximum of #{MAX_SIZE / 1.megabyte}MB"
end
# Content type validation
unless ALLOWED_CONTENT_TYPES.include?(uploaded_file.content_type)
errors << "File type #{uploaded_file.content_type} is not allowed"
end
# Filename validation
filename = File.basename(uploaded_file.original_filename)
unless filename.match?(SAFE_FILENAME_REGEX)
errors << "Filename contains invalid characters"
end
if filename.length > 255
errors << "Filename is too long"
end
# Magic byte validation for images
if uploaded_file.content_type.start_with?('image/')
magic_bytes = uploaded_file.read(8)
uploaded_file.rewind
valid_image = case uploaded_file.content_type
when 'image/jpeg'
magic_bytes.start_with?("\xFF\xD8\xFF")
when 'image/png'
magic_bytes.start_with?("\x89PNG")
when 'image/gif'
magic_bytes.start_with?("GIF87a") || magic_bytes.start_with?("GIF89a")
else
false
end
unless valid_image
errors << "File content does not match declared content type"
end
end
errors
end
end
Common Pitfalls
Partial regex matching creates validation bypass vulnerabilities when developers forget anchors. Without start anchor \A and end anchor \z, regex matches any substring, allowing attackers to append or prepend malicious content that passes validation.
# Vulnerable: matches "user@example.com" anywhere in string
def vulnerable_email?(email)
email.match?(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/)
end
# Attack: vulnerable_email?("attacker<script>@evil.com") => false
# Attack: vulnerable_email?("valid@example.com<script>alert(1)</script>") => true (passes!)
# Secure: requires entire string to match
def secure_email?(email)
email.match?(/\A[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\z/)
end
Validating after type coercion obscures invalid input by converting unexpected data into default values. String-to-integer coercion converts non-numeric strings to zero, causing validation logic to accept invalid input.
# Dangerous: "malicious" coerces to 0, passing validation
age = params[:age].to_i
if age >= 0
process_age(age) # Processes 0 for invalid input
end
# Correct: validate before coercion
if params[:age].match?(/\A\d+\z/)
age = params[:age].to_i
process_age(age) if age >= 0
else
raise ValidationError, "Invalid age format"
end
Client-side validation alone provides no security value, as attackers control client environments and bypass any client-side checks. Client-side validation improves user experience but never substitutes for server-side validation.
Blacklist-based validation fails to account for encoding variations, case sensitivity, and novel attack vectors. Attackers find creative ways to represent malicious content that bypasses blacklist filters.
# Inadequate blacklist approach
def sanitize_dangerous(input)
input.gsub(/script|javascript|onerror/i, '')
end
# Bypassed by: "<scr<script>ipt>alert(1)</script>"
# Bypassed by: "javascript:alert(1)"
# Better: whitelist allowed characters
def validate_safe_input(input)
input.match?(/\A[a-zA-Z0-9\s.,!?-]+\z/)
end
Length validation on byte count rather than character count creates issues with multibyte UTF-8 characters. Database column limits operate on character counts, but byte-based validation may accept strings that exceed database limits.
# Incorrect: byte length vs character length mismatch
def validate_length(text)
text.bytesize <= 100 # May allow >100 characters with multibyte chars
end
# Correct: validate character length
def validate_length(text)
text.length <= 100
end
Insufficient semantic validation accepts syntactically valid but meaningless data. Date validation that accepts February 30th, quantity validation that accepts negative values, or price validation that accepts zero demonstrate semantic validation failures.
# Syntactically valid but semantically invalid
def create_order(quantity, price)
unless quantity.is_a?(Integer)
raise TypeError, "Quantity must be integer"
end
# Missing semantic validation
# Should reject: quantity <= 0, price < 0
Order.create(quantity: quantity, price: price)
end
# Complete validation
def create_order(quantity, price)
unless quantity.is_a?(Integer) && quantity > 0
raise ArgumentError, "Quantity must be positive integer"
end
unless price.is_a?(Numeric) && price >= 0
raise ArgumentError, "Price must be non-negative"
end
Order.create(quantity: quantity, price: price)
end
Trusting deserialized data without validation assumes serialization provides integrity guarantees. Attackers manipulate serialized data before submission, requiring validation after deserialization.
Unicode normalization bypass occurs when validation and application logic use different Unicode normalization forms. Attackers exploit normalization differences to bypass validation filters.
Error message information disclosure reveals validation logic details that assist attackers. Overly specific error messages expose database schema, field constraints, or internal logic that attackers use to craft attacks.
Error Handling & Edge Cases
Validation failures require informative error messages that help legitimate users correct problems without revealing sensitive implementation details. Error messages should identify the field, describe the problem, and suggest corrections without exposing database schema, query structure, or internal validation logic.
class ValidationError < StandardError
attr_reader :field, :message_type
def initialize(field, message_type, details = nil)
@field = field
@message_type = message_type
super(format_message(details))
end
private
def format_message(details)
case @message_type
when :required
"#{@field} is required"
when :format
"#{@field} format is invalid"
when :length
"#{@field} length must be between #{details[:min]} and #{details[:max]} characters"
when :type
"#{@field} must be a #{details[:expected_type]}"
else
"#{@field} is invalid"
end
end
end
Null and undefined value handling requires explicit checking before validation. Ruby's nil values and empty strings represent absence differently from invalid input, requiring separate handling paths.
def validate_optional_field(value)
return true if value.nil? # Optional field, absence is valid
return false if value.empty? # Present but empty is invalid
value.match?(/\A[a-zA-Z0-9]+\z/) # Validate format when present
end
def validate_required_field(value)
return false if value.nil? || value.empty? # Both nil and empty are invalid
value.match?(/\A[a-zA-Z0-9]+\z/)
end
Whitespace handling affects validation depending on field semantics. Leading and trailing whitespace may represent invalid input for some fields while being acceptable for others. Explicit whitespace handling policy prevents inconsistent behavior.
def validate_username(username)
# Usernames should not contain whitespace
return false if username.nil?
return false if username.match?(/\s/)
username.match?(/\A[a-zA-Z0-9_]{3,20}\z/)
end
def validate_display_name(name)
# Display names allow internal whitespace but not leading/trailing
return false if name.nil?
trimmed = name.strip
return false if trimmed.empty?
trimmed.match?(/\A[a-zA-Z0-9\s]{1,50}\z/)
end
Boundary condition testing validates input at extreme values. Minimum and maximum length values, numeric limits, and empty collections represent common boundary conditions that expose validation edge cases.
RSpec.describe Validator do
describe '#validate_age' do
it 'accepts valid ages' do
expect(Validator.validate_age(25)).to be true
end
it 'rejects negative ages' do
expect(Validator.validate_age(-1)).to be false
end
it 'accepts zero age' do
expect(Validator.validate_age(0)).to be true
end
it 'accepts maximum reasonable age' do
expect(Validator.validate_age(150)).to be true
end
it 'rejects unrealistic ages' do
expect(Validator.validate_age(151)).to be false
end
end
end
Encoding validation prevents encoding-based attacks. Input should be validated for proper UTF-8 encoding, with invalid byte sequences rejected before processing.
def validate_encoding(input)
unless input.valid_encoding?
raise ValidationError.new(:input, :encoding, "Invalid UTF-8 encoding")
end
# Additional validation after encoding check
validate_content(input)
end
Array and collection validation requires checking collection type, element count constraints, and validating each element individually. Validation short-circuits on first error or accumulates all errors depending on requirements.
def validate_items(items)
errors = []
unless items.is_a?(Array)
return ["Items must be an array"]
end
if items.empty?
errors << "At least one item is required"
end
if items.length > 100
errors << "Cannot exceed 100 items"
end
items.each_with_index do |item, index|
item_errors = validate_item(item)
item_errors.each do |error|
errors << "Item #{index}: #{error}"
end
end
errors
end
Reference
Common Validation Patterns
| Pattern | Regex | Use Case |
|---|---|---|
| /\A[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}\z/ | Email addresses | |
| Username | /\A[a-zA-Z0-9_]{3,20}\z/ | Alphanumeric usernames |
| UUID | /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i | UUID identifiers |
| Hex Color | /\A#[0-9a-f]{6}\z/i | Hex color codes |
| ISO Date | /\A\d{4}-\d{2}-\d{2}\z/ | YYYY-MM-DD dates |
| Phone (E.164) | /\A+?[1-9]\d{1,14}\z/ | International phone |
| Slug | /\A[a-z0-9-]+\z/ | URL-safe slugs |
| IPv4 | /\A(?:[0-9]{1,3}.){3}[0-9]{1,3}\z/ | IPv4 addresses |
Rails Validation Helpers
| Helper | Parameters | Description |
|---|---|---|
| validates_presence_of | :field | Ensures field is not nil or empty |
| validates_length_of | :field, minimum:, maximum: | Validates string length |
| validates_numericality_of | :field, greater_than:, less_than: | Validates numeric values |
| validates_format_of | :field, with: | Validates against regex pattern |
| validates_inclusion_of | :field, in: | Validates value in allowed set |
| validates_exclusion_of | :field, in: | Rejects values in blacklist |
| validates_uniqueness_of | :field, scope: | Ensures database uniqueness |
| validates_confirmation_of | :field | Validates confirmation field match |
Type Checking Methods
| Method | Returns | Example |
|---|---|---|
| is_a?(Class) | Boolean | value.is_a?(String) |
| kind_of?(Class) | Boolean | value.kind_of?(Numeric) |
| instance_of?(Class) | Boolean | value.instance_of?(Integer) |
| respond_to?(method) | Boolean | value.respond_to?(:each) |
| String#match?(pattern) | Boolean | str.match?(/\d+/) |
| valid_encoding? | Boolean | str.valid_encoding? |
Input Sanitization Functions
| Function | Purpose | Example |
|---|---|---|
| String#strip | Remove leading/trailing whitespace | input.strip |
| String#gsub | Pattern replacement | input.gsub(/[^\w]/, '') |
| String#tr | Character translation | input.tr('^A-Za-z0-9', '') |
| String#encode | Encoding conversion | input.encode('UTF-8', invalid: :replace) |
| CGI.escapeHTML | HTML entity encoding | CGI.escapeHTML(user_input) |
| URI.encode_www_form_component | URL encoding | URI.encode_www_form_component(param) |
Security Validation Checklist
| Check | Purpose | Implementation |
|---|---|---|
| Type validation | Ensure correct data type | Use is_a? or type coercion with validation |
| Format validation | Match expected pattern | Regular expressions with anchors |
| Length constraints | Prevent buffer issues | Check string/array length bounds |
| Range validation | Enforce numeric limits | Compare against min/max values |
| Whitelist characters | Allow only safe characters | Match against approved character set |
| Encoding validation | Prevent encoding attacks | Check valid_encoding? |
| Canonical form | Prevent bypass via encoding | Normalize before validation |
| Business rules | Enforce domain constraints | Custom validation logic |
Error Response Structure
| Field | Type | Description |
|---|---|---|
| status | String | Error status code |
| errors | Array | List of validation errors |
| field | String | Field name with error |
| message | String | Human-readable error message |
| code | String | Machine-readable error code |
| details | Object | Additional error context |
Validation Timing
| Stage | Location | Purpose |
|---|---|---|
| Input reception | Controller/API | Reject malformed requests early |
| Type coercion | Parameter parsing | Convert to expected types |
| Business rules | Service layer | Enforce domain constraints |
| Persistence | Model layer | Final validation before save |
| Output rendering | View layer | Escape for safe display |