By Yutaka Hasoai

Secure Coding in Ruby on Rails

A comprehensive guide to securing your Ruby on Rails applications

security
best-practices
ruby
rails

Introduction

Security is a critical aspect of web application development, and Ruby on Rails (RoR) provides several built-in mechanisms to help developers write secure code. However, understanding and applying these features correctly is essential to prevent vulnerabilities. This guide focuses on secure coding practices in Ruby on Rails to help you build secure, production-ready applications and protect against common security threats.

Common Vulnerabilities in Rails Applications

Before diving into best practices, it’s important to understand common Rails application security risks:

1. SQL Injection in Rails

SQL Injection occurs when attackers manipulate SQL queries by injecting malicious input, allowing them to access, modify, or delete data without authorization. This vulnerability can lead to complete database compromise, data theft, or destruction of critical information. Learn more about SQL Injection vulnerabilities at CWE-89: SQL Injection.

Example Fix:

# BAD: Vulnerable to SQL injection
User.where("email = '#{params[:email]}'")

# GOOD: Uses parameterized queries
User.where(email: params[:email])

More info on Rails SQL Injection

2. Cross-Site Scripting (XSS) in Ruby on Rails

Cross-Site Scripting (XSS) occurs when attackers inject malicious JavaScript code into web pages viewed by other users. When executed in victims' browsers, these scripts can steal sensitive information like cookies, session tokens, or personal data. Rails provides built-in protection against XSS, but developers must use these safeguards correctly to prevent this common vulnerability.

# BAD Pattern - Vulnerable to XSS
# This code directly outputs user input without sanitization
class PostsController < ApplicationController
  def show
    @user_comment = params[:comment]
    # Dangerous! Direct output of user input
    @output = @user_comment.html_safe
  end
end

Fix: Always use Rails’ built-in escaping mechanisms. Avoid calling .html_safe unless absolutely necessary. For manual escaping, use sanitize(user_input).

# GOOD Pattern - Protected against XSS
class PostsController < ApplicationController
  def show
    @user_comment = params[:comment]
    # Safe! Using Rails' built-in sanitization
    @output = sanitize(@user_comment)
    
    # Alternatively, Rails will automatically escape content by default
    # when using regular ERB tags <%= %>
  end
end

More info on Rails XSS

3. CSRF Protection in Rails

Cross-Site Request Forgery (CSRF) tricks users into making unwanted requests to sites where they're authenticated. Learn more about CSRF vulnerabilities at CWE-352: Cross-Site Request Forgery.

# BAD Pattern - Vulnerable to CSRF
class ApplicationController < ActionController::Base
  # Missing CSRF protection!
  # No protect_from_forgery call
end

class UsersController < ApplicationController
  def update_email
    current_user.update(email: params[:email])
    redirect_to profile_path
  end
end
# In view (bad pattern):
<form action="/users/update_email" method="POST">
  <input type="text" name="email">
  <button type="submit">Update Email</button>
</form>

Fix:

  • Ensure protect_from_forgery with: :exception is enabled in controllers.
  • Use form helpers like form_for, form_with, which include CSRF tokens automatically.
# GOOD Pattern - Protected against CSRF
class ApplicationController < ActionController::Base
  # Enable CSRF protection for all controllers
  protect_from_forgery with: :exception
end

class UsersController < ApplicationController
  def update_email
    current_user.update(email: params[:email])
    redirect_to profile_path
  end
end
# In view (good pattern):
<%= form_with(url: update_email_user_path, method: :post) do |f| %>
  <%= f.text_field :email %>
  <%= f.submit "Update Email" %>
<% end %>

4. Insecure Direct Object References (IDOR)

IDOR vulnerabilities in Rails allow attackers to access unauthorized resources by modifying request parameters. Learn more at CWE-639.

Fix:

current_user.resources.find(params[:id])

The fix shown above addresses this vulnerability by scoping database queries to objects that belong to the current user. By using current_user.resources.find(params[:id]) instead of Resource.find(params[:id]), the application ensures that users can only access their own resources, even if they attempt to manipulate the ID parameter in the request. This pattern enforces authorization at the database query level, creating a robust defense against IDOR vulnerabilities.

For more complex authorization requirements, this approach can be combined with policy objects from gems like Pundit to implement fine-grained access controls based on user roles and resource attributes.

5. Mass Assignment Vulnerabilities in Rails

Attackers can modify protected attributes through parameter tampering. Learn more about Mass Assignment vulnerabilities.

Fix:

params.require(:user).permit(:name, :email)

The fix uses Rails' Strong Parameters feature to explicitly whitelist which attributes can be mass-assigned, preventing attackers from setting unauthorized attributes. By requiring a specific parameter namespace (:user) and only permitting specific fields (:name, :email), the application ensures that even if malicious parameters are submitted, only the explicitly allowed attributes will be accepted for mass assignment.

Ruby on Rails Secure Coding Practices

1. Keep Rails Dependencies Up to Date

Outdated gems often contain known vulnerabilities. Use bundle outdated and tools like bundler-audit to stay current.

Best Practices:

  • Avoid unsupported Rails versions
  • Automate dependency checks

2. Use Rails Built-In Security Features

Rails includes powerful protections:

  • CSRF Protection (default)
  • ActiveRecord Query Interface (prevents SQL injection)
  • Content Security Policy (CSP) for resource control

Example CSP Configuration:

Rails.application.config.content_security_policy do |policy|
  policy.default_src :self
  policy.script_src :self, ->(request) { "'nonce-#{Digest::SHA256.base64digest(request.session.id.to_s)}'" }
  policy.style_src :self
end

3. Strong Authentication and Authorization in Rails

Implementing robust authentication and authorization mechanisms is crucial for protecting Rails applications from unauthorized access and privilege escalation attacks. Authentication verifies user identity through secure frameworks like Devise or Clearance, while authorization controls what authenticated users can access using tools like Pundit or CanCanCan.

Best Practices for Authentication:

  • Use secure authentication frameworks
  • Hash passwords using bcrypt or Argon2

Best Practices for Authorization:

  • Implement fine-grained permissions (e.g., Pundit, CanCanCan)
  • Apply the principle of least privilege

4. Validate and Sanitize User Input in Rails

Improper input validation leads to injection attacks. Always validate format and sanitize user-provided data.

Example:

class User < ApplicationRecord
  validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
end

This example demonstrates proper input validation by ensuring that email addresses conform to a standard format using Ruby's built-in URI module. The validation prevents malformed or potentially malicious input from being accepted, which is a critical defense against injection attacks and data corruption.

5. Encrypt Sensitive Data in Ruby on Rails

Best Practices:

  • Use config.force_ssl = true
  • Enable HSTS
  • Store secrets using Rails credentials
  • Encrypt attributes with Lockbox or Rails 8 native support
class User < ApplicationRecord
  encrypts :email
end

6. Secure Configuration Management in Rails

Misconfigurations in Rails applications can expose sensitive information like API keys, database credentials, and user data to potential attackers. Proper configuration management is essential for maintaining a secure application environment and preventing unauthorized access to critical systems. Implementing secure defaults and regularly auditing configuration settings helps minimize the risk of data breaches and other security incidents.

Best Practices:

  • Disable verbose error messages in production
  • Use environment variables for secrets
  • Apply middleware for authentication and authorization

7. Proactive Monitoring and Rails Security Testing

Effective Rails security requires continuous vigilance through a combination of automated tools and manual processes. Implementing a layered approach with vulnerability scanners, penetration testing, and regular code reviews creates a comprehensive security posture. Regular security assessments and staying updated with the latest security patches are essential components of a mature Rails application security strategy.

Tools for Rails Security Scanning:

  • Brakeman (static analysis)
  • Corgea for AI-driven vulnerability detection and automated remediation
  • Sentry, Datadog for live monitoring

8. New Rails 8 Security Enhancements

Rails 8.x introduces:

  • Native encrypted attributes
  • Stricter Content Security Policy
  • Enhanced CSRF protection

Example:

class User < ApplicationRecord
  has_secure_token :auth_token
end

Conclusion

Ruby on Rails application security is not a one-time task—it’s an ongoing responsibility. By following these secure coding practices, you’ll build applications that are resilient against common attack vectors and ready for production deployment. Leverage tools like Corgea, Brakeman, and Lockbox, and stay up to date with the latest Rails versions and security advisories to maintain strong defenses.

Corgea Logo

Find and fix vulnerabilities with Corgea

Scan your codebase and get fixes instantly.

Start for free and no credit card needed.