Complete Guide to Ruby on Rails Security Basics

Complete Rails Security Guide for Beginners – Strong Params, SQL Injection, CSRF

🔐 Complete Rails Security Guide for Beginners

A comprehensive guide to protecting your Ruby on Rails applications with real-world examples, case studies, and practical solutions.

🚀 Why Security Matters in Rails Applications

Security is not just about protecting your application—it’s about protecting your users’ data and trust. Here’s why every Rails developer should prioritize security:

  • Data Protection: Prevent unauthorized access to sensitive user information
  • User Trust: Build confidence in your application’s reliability
  • Legal Compliance: Meet requirements like GDPR, HIPAA, and other regulations
  • Business Continuity: Avoid costly data breaches and downtime
  • Reputation Protection: Maintain your company’s good standing in the market
⚠️ Real Impact: In 2021, a major Rails application suffered a data breach affecting 100,000+ users due to SQL injection. The company lost $2 million in damages and customer trust.

🔍 Understanding Rails Security Concepts

1. Strong Parameters – Your First Line of Defense

Strong parameters prevent mass assignment vulnerabilities by explicitly controlling which attributes can be modified through forms.

What does ‘mass assignable’ mean?

Mass assignment is when you use a single command to set multiple attributes of a model at once, usually from user input (like a form). For example, User.create(params[:user]) will set all attributes provided in params[:user] at once. If you don’t control which attributes can be set, attackers could change sensitive fields (like admin or role) by including them in the form data. That’s why you need to specify which attributes are mass assignable (allowed to be set in bulk) using strong parameters.

❌ Dangerous Code (Without Strong Parameters):

# app/controllers/users_controller.rb def create @user = User.create(params[:user]) # DANGEROUS! # Attacker could send: {user: {name: "John", admin: true}} end

Problem: Users can modify any attribute, including sensitive ones like admin or role.

✅ Safe Code (With Strong Parameters):

# app/controllers/users_controller.rb def create @user = User.create(user_params) end private def user_params params.require(:user).permit(:name, :email, :password) # Only these attributes are allowed end

Benefit: Only specified attributes can be modified, preventing unauthorized access to sensitive fields.

📝 How to Implement Strong Parameters:

  1. Identify which attributes should be mass-assignable
  2. Create a private method for parameter filtering
  3. Use params.require(:model).permit(:attr1, :attr2)
  4. Test with various input scenarios
  5. Regularly review and update permitted parameters

2. SQL Injection Prevention – Never Trust User Input

SQL injection occurs when malicious SQL code is inserted into database queries through user input.

❌ Vulnerable Code (SQL Injection):

# DANGEROUS - SQL Injection vulnerability def search_users email = params[:email] users = User.where("email = '#{email}'") # If email = "'; DROP TABLE users; --" # Query becomes: SELECT * FROM users WHERE email = ''; DROP TABLE users; --' end

Risk: Attacker can execute arbitrary SQL commands, potentially destroying your database.

✅ Safe Code (Parameterized Queries):

# SAFE - Using ActiveRecord parameterized queries def search_users email = params[:email] users = User.where(email: email) # ActiveRecord automatically escapes the input end # Alternative safe approaches: users = User.where("email = ?", params[:email]) users = User.where("email = :email", email: params[:email])

Benefit: User input is properly escaped, preventing SQL injection attacks.

3. CSRF Protection – Preventing Cross-Site Request Forgery

CSRF attacks trick users into performing actions they didn’t intend to perform.

🔒 How Rails Protects Against CSRF:

# Rails automatically includes CSRF protection # In your layout file (app/views/layouts/application.html.erb) <%= csrf_meta_tags %> # In your forms <%= form_with model: @user do |form| %> <%= form.text_field :name %> <%= form.submit %> <% end %> # Rails automatically includes the authenticity token
⚠️ Important: Never disable CSRF protection unless you have a very specific reason and understand the security implications.

4. XSS Prevention – Escaping User Content

Cross-Site Scripting (XSS) allows attackers to inject malicious scripts into your web pages.

❌ Dangerous Code (XSS Vulnerability):

# DANGEROUS - XSS vulnerability <%= raw @user.comment %> # Never use raw with user input! # If comment = "<script>alert('Hacked!')</script>" # This will execute the JavaScript

✅ Safe Code (Proper Escaping):

# SAFE - Rails auto-escapes by default <%= @user.comment %> # Automatically escaped # For trusted content only: <%= sanitize @user.comment %> # Allows safe HTML only <%= simple_format @user.comment %> # Converts line breaks to HTML

Benefit: User input is properly escaped, preventing XSS attacks.

5. Session Security – Protecting User Sessions

Session security is crucial for maintaining user authentication and preventing session hijacking.

❌ Insecure Session Configuration:

# config/application.rb - DANGEROUS config.session_store :cookie_store, key: '_myapp_session' # Sessions stored in cookies without encryption

Risk: Session data can be easily read and modified by attackers.

✅ Secure Session Configuration:

# config/application.rb - SECURE config.session_store :cookie_store, key: '_myapp_session', secure: Rails.env.production?, # HTTPS only in production httponly: true, # Prevent JavaScript access same_site: :lax # CSRF protection # Or use Redis for better security config.session_store :redis_store, servers: ['redis://localhost:6379/0'], key: '_myapp_session', expire_after: 30.minutes

Benefit: Sessions are encrypted, have expiration times, and are protected from XSS and CSRF.

6. Secure File Uploads – Preventing Malicious Files

File uploads can be dangerous if not properly validated and secured.

❌ Dangerous File Upload:

# DANGEROUS - No validation def upload_file file = params[:file] File.write("uploads/#{file.original_filename}", file.read) end # Attacker could upload .php, .exe, or other malicious files

Risk: Attackers can upload executable files, scripts, or files that could compromise your server.

✅ Secure File Upload:

# SAFE - With proper validation class Document < ApplicationRecord has_one_attached :file validates :file, presence: true, content_type: ['image/jpeg', 'image/png', 'application/pdf'], size: { less_than: 5.megabytes } end # In controller def upload_file @document = Document.new(document_params) if @document.save # Process file safely ProcessFileJob.perform_later(@document.id) end end private def document_params params.require(:document).permit(:file) end

Benefit: Only safe file types are allowed, with size limits and virus scanning.

7. Authentication & Authorization - Who Can Do What

Authentication verifies who a user is, while authorization determines what they can do.

❌ Weak Authentication:

# DANGEROUS - No password hashing def create_user user = User.create( email: params[:email], password: params[:password] # Stored in plain text! ) end # DANGEROUS - No authorization checks def edit_post @post = Post.find(params[:id]) # Anyone can edit any post! end

Risk: Passwords are stored in plain text, and users can access unauthorized resources.

✅ Secure Authentication & Authorization:

# SAFE - Using Devise for authentication # Gemfile gem 'devise' # User model class User < ApplicationRecord devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable end # Controller with authorization class PostsController < ApplicationController before_action :authenticate_user! before_action :set_post, only: [:edit, :update, :destroy] before_action :authorize_user!, only: [:edit, :update, :destroy] def edit @post = Post.find(params[:id]) end private def set_post @post = Post.find(params[:id]) end def authorize_user! unless @post.user == current_user redirect_to posts_path, alert: 'Not authorized!' end end end

Benefit: Passwords are hashed, users are authenticated, and access is properly controlled.

8. HTTPS & SSL - Encrypting Data in Transit

HTTPS encrypts data between the client and server, preventing interception and man-in-the-middle attacks.

❌ HTTP (Insecure):

# DANGEROUS - No SSL # All data sent in plain text # Passwords, credit cards, personal info exposed

Risk: All data is transmitted in plain text, easily intercepted by attackers.

✅ HTTPS (Secure):

# config/environments/production.rb config.force_ssl = true # Force HTTPS # config/application.rb config.ssl_options = { hsts: { subdomains: true, preload: true }, redirect: { status: :permanent, port: 443 } }

Benefit: All data is encrypted, protecting sensitive information from interception.

9. Rate Limiting - Preventing Abuse

Rate limiting prevents brute force attacks and abuse by limiting the number of requests from a single source.

❌ No Rate Limiting:

# DANGEROUS - No protection against abuse def login user = User.find_by(email: params[:email]) if user&.valid_password?(params[:password]) sign_in user end end # Attacker can try unlimited password attempts

Risk: Attackers can perform brute force attacks without any restrictions.

✅ With Rate Limiting:

# Gemfile gem 'rack-attack' # config/initializers/rack_attack.rb class Rack::Attack # Limit login attempts throttle('login/ip', limit: 5, period: 20.seconds) do |req| req.ip if req.path == '/users/sign_in' && req.post? end # Block suspicious IPs blocklist('blocklist') do |req| Rack::Attack::Allow2Ban.filter(req.ip, maxretry: 5, findtime: 10.minutes, bantime: 1.hour) do req.path == '/users/sign_in' && req.post? end end end

Benefit: Prevents brute force attacks and protects against abuse.

10. Security Headers - Additional Protection Layers

Security headers provide additional protection against various attacks by instructing browsers how to handle your website's content and interactions.

What are Security Headers?

Security headers are HTTP response headers that tell browsers how to behave when loading your website. They act as an additional layer of defense against common web attacks like XSS, clickjacking, and data injection.

❌ Missing Security Headers:

# DANGEROUS - No security headers configured # Browser receives no security instructions # Vulnerable to XSS, clickjacking, MIME sniffing, and other attacks # Example vulnerable response: HTTP/1.1 200 OK Content-Type: text/html # No security headers present!

Risk: Application is vulnerable to various client-side attacks because browsers don't know how to protect against them.

✅ With Security Headers:

# SAFE - Comprehensive security headers HTTP/1.1 200 OK Content-Type: text/html Content-Security-Policy: default-src 'self'; script-src 'self' X-Frame-Options: DENY X-Content-Type-Options: nosniff X-XSS-Protection: 1; mode=block Strict-Transport-Security: max-age=31536000; includeSubDomains Referrer-Policy: strict-origin-when-cross-origin

Benefit: Browsers are instructed to protect against various attacks and follow security best practices.

Understanding Each Security Header:

1. Content Security Policy (CSP)

What it does: Controls which resources (scripts, styles, images) can be loaded and executed.

Why it's important: Prevents XSS attacks by blocking unauthorized scripts from running.

# Basic CSP Example Content-Security-Policy: default-src 'self'; script-src 'self' # What this means: # - default-src 'self': Only load resources from same origin # - script-src 'self': Only execute scripts from same origin # Advanced CSP Example Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://api.example.com; frame-src 'none'; object-src 'none'

2. X-Frame-Options

What it does: Prevents your website from being embedded in iframes on other sites.

Why it's important: Protects against clickjacking attacks where attackers trick users into clicking on hidden elements.

# DENY - No embedding allowed X-Frame-Options: DENY # SAMEORIGIN - Only same site can embed X-Frame-Options: SAMEORIGIN # ALLOW-FROM - Specific sites can embed (deprecated) X-Frame-Options: ALLOW-FROM https://trusted-site.com

3. X-Content-Type-Options

What it does: Prevents browsers from MIME-sniffing (guessing file types).

Why it's important: Stops browsers from executing files that might be disguised as images or other safe content.

# Prevents MIME sniffing X-Content-Type-Options: nosniff # Example attack this prevents: # Attacker uploads a file named "image.jpg" # but it's actually a JavaScript file # Without this header, browser might execute it

4. X-XSS-Protection

What it does: Enables browser's built-in XSS protection.

Why it's important: Provides an additional layer of XSS protection, though CSP is more effective.

# Enable XSS protection X-XSS-Protection: 1 # Enable XSS protection with block mode X-XSS-Protection: 1; mode=block # Disable XSS protection (not recommended) X-XSS-Protection: 0

5. Strict-Transport-Security (HSTS)

What it does: Forces browsers to use HTTPS for future requests.

Why it's important: Prevents man-in-the-middle attacks and ensures encrypted communication.

# Basic HSTS Strict-Transport-Security: max-age=31536000 # HSTS with subdomains Strict-Transport-Security: max-age=31536000; includeSubDomains # HSTS with preload (for browser vendors) Strict-Transport-Security: max-age=31536000; includeSubDomains; preload # What max-age=31536000 means: # 31,536,000 seconds = 1 year # Browser will remember to use HTTPS for 1 year

6. Referrer-Policy

What it does: Controls how much referrer information is sent to other sites.

Why it's important: Protects user privacy by limiting what information is leaked to external sites.

# No referrer sent Referrer-Policy: no-referrer # Send referrer only to same origin Referrer-Policy: same-origin # Send referrer to same origin and HTTPS sites Referrer-Policy: strict-origin # Send referrer to same origin and HTTPS sites, downgrade to no referrer Referrer-Policy: strict-origin-when-cross-origin # Send full referrer (least secure) Referrer-Policy: unsafe-url

How to Implement Security Headers in Rails:

Method 1: Using the secure_headers gem (Recommended)

# Gemfile gem 'secure_headers' # config/initializers/secure_headers.rb SecureHeaders::Configuration.default do |config| # Content Security Policy config.csp = { default_src: %w('self'), script_src: %w('self' 'unsafe-inline'), style_src: %w('self' 'unsafe-inline' https://fonts.googleapis.com), img_src: %w('self' data: https:), font_src: %w('self' https://fonts.gstatic.com), connect_src: %w('self'), frame_src: %w('none'), object_src: %w('none'), media_src: %w('self'), worker_src: %w('self'), manifest_src: %w('self'), base_uri: %w('self'), form_action: %w('self'), frame_ancestors: %w('none'), upgrade_insecure_requests: true } # Other security headers config.hsts = "max-age=31536000; includeSubDomains; preload" config.x_frame_options = "DENY" config.x_content_type_options = "nosniff" config.x_xss_protection = "1; mode=block" config.referrer_policy = "strict-origin-when-cross-origin" # Remove X-Powered-By header config.remove_x_powered_by = true end

Method 2: Manual Implementation in ApplicationController

# app/controllers/application_controller.rb class ApplicationController < ActionController::Base before_action :set_security_headers private def set_security_headers # Content Security Policy response.headers['Content-Security-Policy'] = [ "default-src 'self'", "script-src 'self' 'unsafe-inline'", "style-src 'self' 'unsafe-inline'", "img-src 'self' data: https:", "frame-src 'none'", "object-src 'none'" ].join('; ') # Other security headers response.headers['X-Frame-Options'] = 'DENY' response.headers['X-Content-Type-Options'] = 'nosniff' response.headers['X-XSS-Protection'] = '1; mode=block' response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains' response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin' # Remove X-Powered-By header response.headers.delete('X-Powered-By') end end

Method 3: Using Rack Middleware

# config/application.rb config.middleware.use Rack::Deflater # Create custom middleware # lib/security_headers.rb class SecurityHeaders def initialize(app) @app = app end def call(env) status, headers, response = @app.call(env) headers['Content-Security-Policy'] = "default-src 'self'; script-src 'self'" headers['X-Frame-Options'] = 'DENY' headers['X-Content-Type-Options'] = 'nosniff' headers['X-XSS-Protection'] = '1; mode=block' headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains' headers['Referrer-Policy'] = 'strict-origin-when-cross-origin' [status, headers, response] end end # config/application.rb config.middleware.use SecurityHeaders

Testing Your Security Headers:

Using curl to test headers:

# Test security headers curl -I https://your-app.com # Expected output: HTTP/1.1 200 OK Content-Security-Policy: default-src 'self'; script-src 'self' X-Frame-Options: DENY X-Content-Type-Options: nosniff X-XSS-Protection: 1; mode=block Strict-Transport-Security: max-age=31536000; includeSubDomains Referrer-Policy: strict-origin-when-cross-origin

Online Security Header Checkers:

  • SecurityHeaders.com - Comprehensive header analysis
  • Mozilla Observatory - Security scanning tool
  • Google Lighthouse - Performance and security audit
  • OWASP ZAP - Security testing tool

Common CSP Violations and Solutions:

Problem: External scripts blocked

# Error in browser console: # Refused to load the script 'https://cdn.jsdelivr.net/script.js' # because it violates the following Content Security Policy directive: # "script-src 'self'" # Solution: Add the domain to script-src Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.jsdelivr.net;

Problem: Inline styles blocked

# Error: Inline styles blocked by CSP # Solution: Add 'unsafe-inline' to style-src Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline';
⚠️ Important Notes:
  • Start with a restrictive CSP and gradually allow necessary resources
  • Test thoroughly in development before deploying to production
  • Monitor browser console for CSP violations
  • Use CSP reporting to identify violations in production
  • Consider using nonces or hashes instead of 'unsafe-inline' for better security
🎯 Best Practices:
  • Always use HTTPS in production
  • Implement security headers early in development
  • Regularly audit your security headers
  • Use the secure_headers gem for easier management
  • Monitor for violations and adjust policies as needed

11. Input Validation & Sanitization

Always validate and sanitize user input before processing it.

❌ No Input Validation:

# DANGEROUS - No validation def create_comment Comment.create( content: params[:content], # Could contain anything! user_id: current_user.id ) end

Risk: Malicious input can cause various security issues.

✅ With Input Validation:

# SAFE - With validation class Comment < ApplicationRecord belongs_to :user validates :content, presence: true, length: { maximum: 1000 } validates :content, format: { without: /<script>/i } before_save :sanitize_content private def sanitize_content self.content = ActionController::Base.helpers.sanitize(content) end end # In controller def create_comment @comment = current_user.comments.build(comment_params) if @comment.save redirect_to @comment.post else render :new end end private def comment_params params.require(:comment).permit(:content) end

Benefit: Input is validated and sanitized, preventing various attacks.

12. Error Handling - Don't Expose Sensitive Information

Proper error handling prevents information leakage that could help attackers.

❌ Dangerous Error Handling:

# DANGEROUS - Exposes sensitive info def show_user @user = User.find(params[:id]) rescue ActiveRecord::RecordNotFound => e render text: "User not found: #{e.message}" # Exposes internal details end

Risk: Error messages can reveal database structure, file paths, and other sensitive information.

✅ Secure Error Handling:

# SAFE - Generic error messages def show_user @user = User.find(params[:id]) rescue ActiveRecord::RecordNotFound redirect_to users_path, alert: 'User not found' end # config/environments/production.rb config.consider_all_requests_local = false config.action_controller.perform_caching = true # Custom error pages # public/404.html, public/500.html

Benefit: Generic error messages don't reveal sensitive information to attackers.

🌐 Real-World Case Studies & Solutions

Case Study 1: GitHub Mass Assignment Vulnerability (2012)

Problem: GitHub had a mass assignment vulnerability that allowed attackers to add themselves as collaborators to any repository.

Attack Vector: Attackers could send requests with {"user": {"role": "admin"}} parameters.

Solution: GitHub implemented strong parameters and updated to Rails 3.2.3+.

# Before (Vulnerable) def create @user = User.create(params[:user]) end # After (Secure) def create @user = User.create(user_params) end private def user_params params.require(:user).permit(:name, :email, :password) # role is NOT permitted end

Case Study 2: E-commerce SQL Injection (2018)

Problem: An e-commerce site had SQL injection in their search functionality, allowing attackers to extract customer data.

Attack Vector: Search query: '; SELECT * FROM users; --

Solution: Implemented parameterized queries and input validation.

# Before (Vulnerable) def search_products query = params[:search] @products = Product.where("name LIKE '%#{query}%'") end # After (Secure) def search_products query = params[:search] @products = Product.where("name LIKE ?", "%#{query}%") end

Case Study 3: Social Media XSS Attack (2020)

Problem: A social media platform allowed users to post HTML content, leading to XSS attacks.

Attack Vector: Users posted comments with <script>alert('XSS')</script>

Solution: Implemented proper content sanitization and CSP headers.

# Before (Vulnerable) <%= raw @comment.content %> # After (Secure) <%= sanitize @comment.content, tags: %w(p br strong em) %> # Plus Content Security Policy # config/initializers/content_security_policy.rb Rails.application.config.content_security_policy do |policy| policy.default_src :self policy.script_src :self end

Case Study 4: E-commerce Session Hijacking (2019)

Problem: An e-commerce site had insecure session management, allowing attackers to hijack user sessions.

Attack Vector: Attackers intercepted unencrypted session cookies and used them to access user accounts.

Solution: Implemented secure session configuration with HTTPS-only cookies and Redis storage.

# Before (Vulnerable) config.session_store :cookie_store, key: '_shop_session' # After (Secure) config.session_store :redis_store, servers: ['redis://localhost:6379/0'], key: '_shop_session', secure: true, httponly: true, same_site: :strict, expire_after: 30.minutes

Case Study 5: File Upload Vulnerability (2021)

Problem: A document sharing platform allowed upload of executable files, leading to server compromise.

Attack Vector: Attackers uploaded .php files disguised as documents, which were then executed by the web server.

Solution: Implemented strict file validation and secure storage outside web root.

# Before (Vulnerable) def upload_document file = params[:file] File.write("public/uploads/#{file.original_filename}", file.read) end # After (Secure) class Document < ApplicationRecord has_one_attached :file validates :file, content_type: ['application/pdf', 'application/msword'], size: { less_than: 10.megabytes } end # Store outside web root config.active_storage.service = :local config.active_storage.service_urls_expire_in = 1.hour

Case Study 6: Weak Authentication Breach (2018)

Problem: A SaaS platform stored passwords in plain text and had no rate limiting on login attempts.

Attack Vector: Attackers used brute force attacks to crack weak passwords and gain admin access.

Solution: Implemented Devise with bcrypt hashing and rack-attack rate limiting.

# Before (Vulnerable) def authenticate user = User.find_by(email: params[:email]) if user.password == params[:password] # Plain text! session[:user_id] = user.id end end # After (Secure) # Gemfile gem 'devise' gem 'rack-attack' # User model class User < ApplicationRecord devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable end # Rate limiting class Rack::Attack throttle('login/ip', limit: 5, period: 20.seconds) do |req| req.ip if req.path == '/users/sign_in' && req.post? end end

Case Study 7: HTTP Data Interception (2017)

Problem: A banking application was accessible via HTTP, allowing man-in-the-middle attacks.

Attack Vector: Attackers intercepted unencrypted traffic containing credit card information and login credentials.

Solution: Forced HTTPS and implemented HSTS headers.

# Before (Vulnerable) # config/environments/production.rb # No SSL configuration # All data transmitted in plain text # After (Secure) # config/environments/production.rb config.force_ssl = true # config/application.rb config.ssl_options = { hsts: { subdomains: true, preload: true }, redirect: { status: :permanent, port: 443 } }

Case Study 8: Missing Security Headers (2022)

Problem: A news website was vulnerable to clickjacking and XSS due to missing security headers.

Attack Vector: Attackers embedded the site in iframes and injected malicious scripts through user comments.

Solution: Implemented comprehensive security headers including CSP and X-Frame-Options.

# Before (Vulnerable) # No security headers configured # Vulnerable to XSS, clickjacking, MIME sniffing # After (Secure) # Gemfile gem 'secure_headers' # config/initializers/secure_headers.rb SecureHeaders::Configuration.default do |config| config.csp = { default_src: %w('self'), script_src: %w('self'), style_src: %w('self' 'unsafe-inline'), frame_src: %w('none') } config.x_frame_options = "DENY" config.x_content_type_options = "nosniff" end

Case Study 9: Input Validation Bypass (2020)

Problem: A forum application had weak input validation, allowing SQL injection through comment fields.

Attack Vector: Attackers posted comments containing SQL injection payloads that were executed by the application.

Solution: Implemented comprehensive input validation and sanitization.

# Before (Vulnerable) def create_comment Comment.create( content: params[:content], # No validation! user_id: current_user.id ) end # After (Secure) class Comment < ApplicationRecord validates :content, presence: true, length: { maximum: 1000 }, format: { without: /script|javascript/i } before_save :sanitize_content private def sanitize_content self.content = ActionController::Base.helpers.sanitize(content) end end

Case Study 10: Information Disclosure (2019)

Problem: A web application exposed sensitive information through detailed error messages.

Attack Vector: Attackers triggered errors to learn about database structure, file paths, and internal system details.

Solution: Implemented secure error handling and custom error pages.

# Before (Vulnerable) def show_user @user = User.find(params[:id]) rescue ActiveRecord::RecordNotFound => e render text: "User not found: #{e.message}" # Exposes details end # After (Secure) def show_user @user = User.find(params[:id]) rescue ActiveRecord::RecordNotFound redirect_to users_path, alert: 'User not found' end # config/environments/production.rb config.consider_all_requests_local = false config.action_controller.perform_caching = true # Custom error pages # public/404.html, public/500.html

💡 20 Interview Questions & Detailed Answers

1. What are strong parameters and why are they important?

Answer: Strong parameters are a Rails security feature that prevents mass assignment vulnerabilities by explicitly controlling which attributes can be modified through forms. They're important because they prevent attackers from modifying sensitive attributes like admin, role, or permissions.

# Example def user_params params.require(:user).permit(:name, :email, :password) # Only these attributes are allowed end

2. How does Rails prevent CSRF attacks?

Answer: Rails includes CSRF protection by default using authenticity tokens. Every form includes a hidden token that's verified on POST/PUT/PATCH/DELETE requests. If the token doesn't match, the request is rejected.

3. What is SQL injection and how do you prevent it in Rails?

Answer: SQL injection occurs when malicious SQL code is inserted into database queries. Prevent it by using ActiveRecord's parameterized queries instead of string interpolation.

# BAD: User.where("email = '#{params[:email]}'") # GOOD: User.where(email: params[:email])

4. What is XSS and how does Rails protect against it?

Answer: Cross-Site Scripting (XSS) allows attackers to inject malicious scripts. Rails auto-escapes output by default. Use raw only for trusted content.

5. How do you secure file uploads in Rails?

Answer: Validate file types, check file size, scan for viruses, store files outside web root, and use secure file names.

# Example validation validates :avatar, presence: true, file_size: { less_than: 5.megabytes }, file_content_type: { allow: ['image/jpeg', 'image/png'] }

6. What is the difference between `permit` and `require` in strong parameters?

Answer: require ensures the parameter is present (raises error if missing), while permit specifies which attributes are allowed for mass assignment.

7. How do you implement rate limiting in Rails?

Answer: Use gems like rack-attack to limit requests per IP address or user account.

8. What are secure headers and how do you implement them?

Answer: Security headers like CSP, HSTS, and X-Frame-Options protect against various attacks. Use the secure_headers gem.

9. How do you handle authentication securely in Rails?

Answer: Use established gems like Devise, implement proper password hashing (bcrypt), use HTTPS, and implement session management.

10. What is the danger of using `eval` or `instance_eval` with user input?

Answer: It allows code execution, creating a major security vulnerability. Never use these methods with user-controlled input.

11. How do you prevent session hijacking?

Answer: Use secure cookies, HTTPS, proper session configuration, and consider session timeout.

12. What is the purpose of `config.force_ssl = true`?

Answer: It forces all requests to use HTTPS, redirecting HTTP requests to HTTPS automatically.

13. How do you validate and sanitize user input?

Answer: Use ActiveRecord validations, strong parameters, and sanitize methods for HTML content.

14. What is the difference between `permit` and `permit!`?

Answer: permit allows specific attributes, while permit! allows all attributes (dangerous, avoid using).

15. How do you implement proper error handling without exposing sensitive information?

Answer: Use custom error pages, log errors securely, and never expose database structure or stack traces in production.

16. What is the purpose of `config.secret_key_base`?

Answer: It's used to sign and encrypt cookies and sessions. Keep it secret and use environment variables.

17. How do you prevent clickjacking attacks?

Answer: Use the X-Frame-Options header to prevent your site from being embedded in iframes.

18. What is the difference between `before_action` and `around_action` for security?

Answer: before_action runs before the action, while around_action wraps the entire action execution.

19. How do you implement proper logging for security events?

Answer: Log authentication attempts, failed requests, and suspicious activities without logging sensitive data.

20. What is the purpose of `config.action_dispatch.perform_deep_munge = false`?

Answer: It disables Rails' automatic parameter filtering. Generally, keep it enabled for security unless you have specific needs.

🛠️ Essential Security Tools & Gems

  • Devise: Complete authentication solution with built-in security features
    Provides user registration, login, password reset, and session management
  • Brakeman: Static code analyzer that finds security vulnerabilities
    Scans your Rails code for common security issues and generates reports
  • Bundler-audit: Checks your Gemfile.lock for known vulnerabilities
    Identifies gems with security issues and suggests updates
  • Rack::Attack: Middleware for throttling and blocking abusive requests
    Prevents brute force attacks and rate limiting
  • Secure Headers: Easily configure HTTP security headers
    Implements CSP, HSTS, X-Frame-Options, and other security headers
  • CanCanCan: Authorization gem for role-based access control
    Manages user permissions and access control
  • Rails Credentials: Built-in encrypted credentials management
    Securely store API keys, passwords, and other secrets

📦 How to Install and Use Security Gems:

# Gemfile gem 'devise' # Authentication gem 'brakeman' # Security scanner gem 'bundler-audit' # Vulnerability checker gem 'rack-attack' # Rate limiting gem 'secure_headers' # Security headers # Install bundle install # Run security checks bundle exec brakeman bundle exec bundle-audit

✅ Security Best Practices Checklist

  • Always use strong parameters - Never trust user input for mass assignment
  • Keep Rails and gems updated - Regularly update to patch security vulnerabilities
  • Use HTTPS everywhere - Force SSL in production environments
  • Validate and sanitize input - Check all user input before processing
  • Implement proper authentication - Use established gems like Devise
  • Use environment variables - Never hardcode secrets in your code
  • Enable security headers - Implement CSP, HSTS, and other headers
  • Log security events - Monitor for suspicious activities
  • Regular security audits - Use tools like Brakeman and Bundler-audit
  • Implement rate limiting - Prevent brute force and abuse attacks
  • Secure file uploads - Validate file types and scan for malware
  • Use parameterized queries - Prevent SQL injection attacks
  • Escape user content - Prevent XSS attacks
  • Implement proper error handling - Don't expose sensitive information
  • Use secure session management - Configure secure cookies and timeouts
🎯 Pro Tip: Start with the basics (strong parameters, HTTPS, input validation) and gradually implement more advanced security measures as your application grows.

Learn more about Rails

40 thoughts on “Complete Guide to Ruby on Rails Security Basics”

Comments are closed.

Scroll to Top