DEV Community

Harsh patel
Harsh patel

Posted on

7 1

Essential Security Best Practices for Ruby on Rails

By Harsh Patel, Cyber security Enthusiast & Senior Ruby on Rails Dev

Ruby on Rails has empowered countless developers to build web applications rapidly and elegantly. However, with great power comes great responsibility. In today’s threat landscape, securing your Rails application isn’t optional—it’s an absolute necessity. In this comprehensive guide, I’ll walk you through essential security best practices that every Rails developer, from intermediate to advanced, should understand and implement immediately to protect their applications against common vulnerabilities and emerging threats.


Table of Contents

  1. Introduction and Context
  2. Key Security Best Practices in Ruby on Rails
    1. Protection Against SQL Injection
    2. Mitigation of Cross-Site Scripting (XSS)
    3. Secure Management of Secrets, Credentials, and Sensitive Configuration Data
    4. Proper Session Management and Secure Cookies
    5. Cross-Site Request Forgery (CSRF) Protection Strategies
    6. Secure File Uploads and Handling Attachments
    7. Robust Authentication and Authorization
    8. Continuous Security Monitoring and Auditing
  3. Additional Gems, Libraries, and Tools
  4. Security Best Practices Checklist
  5. Conclusion

Introduction and Context

In the world of web development, security is a cornerstone of sustainable and trustworthy software. With Rails’ “convention over configuration” philosophy, many security features are built-in—but this does not mean you can afford to be complacent. Web applications are constantly targeted by attackers exploiting weaknesses such as:

  • SQL Injection: Malicious queries that manipulate your database.
  • Cross-Site Scripting (XSS): Injection of malicious scripts into webpages.
  • Session Hijacking: Stealing or manipulating session data.
  • Cross-Site Request Forgery (CSRF): Unauthorized commands transmitted from a user that the web application trusts.
  • Insecure File Uploads: Exploiting file attachments to execute arbitrary code.
  • Exposure of Sensitive Data: Leaking secrets and configuration details.

For a production-grade Rails application, a proactive, layered security approach is essential. The following best practices cover common vulnerabilities and provide actionable code examples and explanations that you can integrate into your projects immediately.


Key Security Best Practices in Ruby on Rails

1. Protection Against SQL Injection

Definition:

SQL Injection occurs when user input is improperly sanitized, allowing attackers to modify SQL queries. This can lead to data theft, data loss, or unauthorized administrative actions.

How Vulnerabilities Arise:

Using string interpolation to build SQL queries:

# Vulnerable code example:
name = params[:name]
@projects = Project.where("name LIKE '#{name}'")
Enter fullscreen mode Exit fullscreen mode

Best Practice Implementation:

Use ActiveRecord’s parameterized queries and built-in sanitization methods to prevent SQL injection.

# Secure code example:
@projects = Project.where("name LIKE ?", "%#{ActiveRecord::Base.sanitize_sql_like(params[:name])}%")
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Parameterized queries automatically escape dangerous characters.
  • sanitize_sql_like is particularly useful when using wildcards, ensuring that special SQL characters are handled safely.

2. Mitigation of Cross-Site Scripting (XSS)

Definition:

XSS vulnerabilities occur when attackers inject malicious JavaScript or HTML into your web pages, potentially stealing user data or executing unauthorized actions in the user’s browser.

How Vulnerabilities Arise:

Directly outputting user input without proper sanitization:

<!-- Vulnerable ERB template example: -->
<%= raw @product.description %>
Enter fullscreen mode Exit fullscreen mode

Best Practice Implementation:

Leverage Rails’ automatic HTML escaping and use sanitization helpers when you need to allow limited HTML.

<!-- Secure ERB template example: -->
<%= @product.description %>
Enter fullscreen mode Exit fullscreen mode

If you must allow HTML:

# Using sanitize helper to allow specific tags:
allowed_tags = %w[b i u p br]
@safe_content = sanitize(params[:content], tags: allowed_tags)
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Rails auto-escapes by default to prevent XSS.
  • The sanitize helper can be used to whitelist safe HTML tags when user-generated content is required.

3. Secure Management of Secrets, Credentials, and Sensitive Configuration Data

Definition:

Storing sensitive information such as API keys, database credentials, and secret tokens in plaintext or source control exposes your application to severe risks.

How Vulnerabilities Arise:

Including secrets directly in configuration files:

# Bad practice: config/secrets.yml containing plain-text credentials
production:
  secret_key_base: "super_secret_key"
Enter fullscreen mode Exit fullscreen mode

Best Practice Implementation:

Utilize Rails’ encrypted credentials and environment variables to store sensitive data securely.

# To edit credentials in Rails 6+:
EDITOR="vim" rails credentials:edit
Enter fullscreen mode Exit fullscreen mode

Within the encrypted credentials file, store secrets securely:

# config/credentials.yml.enc (example content)
aws:
  access_key_id: your_access_key_id
  secret_access_key: your_secret_access_key
secret_key_base: your_production_secret_key
Enter fullscreen mode Exit fullscreen mode

And access them in your application:

# Accessing credentials securely:
Rails.application.credentials.aws[:access_key_id]
Rails.application.credentials.secret_key_base
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Rails encrypted credentials ensure that sensitive data remains encrypted at rest and is only accessible with the master key, which should never be committed to source control.
  • Environment variables and tools like dotenv-rails can further manage configuration outside your codebase.

4. Proper Session Management and Secure Cookies

Definition:

Sessions track user state across requests, but improperly managed sessions can lead to hijacking or replay attacks.

How Vulnerabilities Arise:

Using default cookie settings without security enhancements:

# Default session store (potential risk):
YourApp::Application.config.session_store :cookie_store, key: '_your_app_session'
Enter fullscreen mode Exit fullscreen mode

Best Practice Implementation:

Enhance session security by using server-side session stores and setting secure cookie options.

ActiveRecord Session Store:

# In Gemfile:
gem 'activerecord-session_store'

# Generate migration and migrate:
rails generate active_record:session_migration
rails db:migrate

# Configure session store:
YourApp::Application.config.session_store :active_record_store, key: '_your_app_session'
Enter fullscreen mode Exit fullscreen mode

Cookie Security Enhancements:

# config/environments/production.rb:
Rails.application.configure do
  config.force_ssl = true  # Force HTTPS
  config.session_store :cookie_store, key: '_your_app_session', secure: Rails.env.production?, httponly: true, same_site: :lax
end
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Server-side session stores reduce exposure as session data is kept on the server.
  • Secure cookie flags (secure, httponly, and same_site) ensure cookies are only sent over HTTPS, not accessible via JavaScript, and provide cross-site request protections.

5. Cross-Site Request Forgery (CSRF) Protection Strategies

Definition:

CSRF attacks trick authenticated users into submitting unwanted actions on web applications without their consent.

How Vulnerabilities Arise:

An attacker may craft a malicious request that leverages a user’s authenticated session.

Best Practice Implementation:

Rails includes built-in CSRF protection. Ensure it is enabled and properly configured in your controllers.

# In ApplicationController:
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception  # Raises an exception if CSRF token verification fails
end
Enter fullscreen mode Exit fullscreen mode

Example Form with CSRF Token:

<%= form_with model: @project do |form| %>
  <%= form.label :name %>
  <%= form.text_field :name %>
  <%= form.submit "Save" %>
<% end %>
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • protect_from_forgery automatically inserts an authenticity token into forms, which Rails verifies on submission.
  • This built-in mechanism defends against CSRF by ensuring that form submissions originate from trusted sources.

6. Secure File Uploads and Handling Attachments

Definition:

File uploads can be exploited to execute arbitrary code if not properly validated, potentially leading to server compromise.

How Vulnerabilities Arise:

Accepting file uploads without validating file types, sizes, or scanning for malware:

# Vulnerable approach: blindly saving uploaded files
def upload
  File.open(Rails.root.join('public', 'uploads', params[:file].original_filename), 'wb') do |file|
    file.write(params[:file].read)
  end
end
Enter fullscreen mode Exit fullscreen mode

Best Practice Implementation:

Use well-maintained libraries such as ActiveStorage or Shrine to handle file uploads securely.

ActiveStorage Example:

  1. Setup ActiveStorage:
rails active_storage:install
rails db:migrate
Enter fullscreen mode Exit fullscreen mode
  1. Attach Files to Models:
class User < ApplicationRecord
  has_one_attached :avatar
end
Enter fullscreen mode Exit fullscreen mode
  1. Controller Example with Validation:
class UsersController < ApplicationController
  def update
    @user = current_user
    if params[:user][:avatar]
      # Validate file type and size here (e.g., using custom validations or ActiveStorage validations)
      @user.avatar.attach(params[:user][:avatar])
    end
    if @user.save
      redirect_to @user, notice: 'Profile updated successfully.'
    else
      render :edit
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • ActiveStorage abstracts file handling securely by storing files externally (e.g., Amazon S3, Google Cloud Storage) and providing validations.
  • Always validate file type and size before accepting uploads.

7. Robust Authentication and Authorization

Definition:

Ensuring that users are who they claim to be (authentication) and that they have permissions to access certain resources (authorization) is fundamental to application security.

How Vulnerabilities Arise:

Weak authentication mechanisms or misconfigured access control can lead to unauthorized access.

Best Practice Implementation:

Leverage industry-standard gems like Devise for authentication and Pundit or Cancancan for authorization.

Devise Installation and Setup:

# In Gemfile:
gem 'devise'
Enter fullscreen mode Exit fullscreen mode
# Install Devise:
rails generate devise:install
rails generate devise User
rails db:migrate
Enter fullscreen mode Exit fullscreen mode

Route Protection Example:

Rails.application.routes.draw do
  authenticate :user do
    resources :projects  # Only authenticated users can access these routes
  end
  devise_for :users
  root to: 'home#index'
end
Enter fullscreen mode Exit fullscreen mode

Pundit for Authorization Example:

  1. Install Pundit:
# In Gemfile:
gem 'pundit'
Enter fullscreen mode Exit fullscreen mode
  1. Include in ApplicationController:
class ApplicationController < ActionController::Base
  include Pundit
  # Rescue from unauthorized access
  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

  private

  def user_not_authorized
    flash[:alert] = "You are not authorized to perform this action."
    redirect_to(request.referrer || root_path)
  end
end
Enter fullscreen mode Exit fullscreen mode
  1. Define a Policy:
# app/policies/project_policy.rb
class ProjectPolicy < ApplicationPolicy
  def update?
    user.admin? || record.owner == user
  end
end
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Devise provides secure authentication flows.
  • Pundit (or Cancancan) enables granular control over resource access, ensuring that users only perform actions for which they have explicit permission.

8. Continuous Security Monitoring and Auditing

Definition:

Security is not a one-time task. Continuous monitoring, auditing, and automated security testing help identify vulnerabilities early and ensure ongoing protection.

How Vulnerabilities Arise:

Without regular audits, even secure applications can accumulate vulnerabilities over time due to outdated dependencies or evolving threats.

Best Practice Implementation:

  • Static Analysis Tools:

    Use tools like Brakeman to scan your codebase for common vulnerabilities.

  • Dependency Auditing:

    Utilize bundler-audit to monitor for vulnerabilities in your Gemfile.lock.

  • Security Headers:

    Use the secure_headers gem to ensure that HTTP security headers are correctly configured.


Detailed Overview: Brakeman

Brakeman is a static analysis tool designed specifically for Rails applications. It scans your source code without executing it, identifying common security vulnerabilities such as SQL injection, XSS, and unsafe mass assignment.

Installation and Usage

  1. Install Brakeman:
gem install brakeman
Enter fullscreen mode Exit fullscreen mode
  1. Run Brakeman in your Rails project:
brakeman
Enter fullscreen mode Exit fullscreen mode

This command analyzes your code and outputs a report of potential vulnerabilities.

  1. Integrate with CI/CD: You can add Brakeman to your CI pipeline (e.g., in a GitHub Actions workflow) to ensure continuous monitoring:
# .github/workflows/security.yml
name: Security Scan

on: [push, pull_request]

jobs:
  brakeman:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.0'
      - name: Install dependencies
        run: bundle install --jobs 4 --retry 3
      - name: Run Brakeman
        run: bundle exec brakeman -q -o brakeman-report.json
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Brakeman helps you identify vulnerabilities early in development.
  • Regular scans ensure that new vulnerabilities introduced by code changes are caught before deployment.

Detailed Overview: bundler-audit

bundler-audit checks your Gemfile.lock for gems with known vulnerabilities. It compares your dependencies against a database of advisories and alerts you if any are found.

Installation and Usage

  1. Install bundler-audit:
gem install bundler-audit
Enter fullscreen mode Exit fullscreen mode
  1. Run bundler-audit in your Rails project:
bundler-audit check --update
Enter fullscreen mode Exit fullscreen mode

This command will:

  • Update the advisory database.
  • Scan your Gemfile.lock for known vulnerabilities.
  • Print a report with recommendations on how to update or patch the affected gems.
  1. Integrate with CI/CD: For continuous monitoring, include bundler-audit in your CI pipeline:
# .github/workflows/dependency_audit.yml
name: Dependency Audit

on: [push, pull_request]

jobs:
  bundler_audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.0'
      - name: Install dependencies
        run: bundle install --jobs 4 --retry 3
      - name: Run bundler-audit
        run: bundle exec bundler-audit check --update
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • bundler-audit ensures your project dependencies remain secure by flagging outdated or vulnerable gems.
  • Integration with your CI pipeline automates the process and keeps you informed of any new vulnerabilities.

Example of Secure Headers Configuration:

# Gemfile:
gem 'secure_headers'
Enter fullscreen mode Exit fullscreen mode
# config/initializers/secure_headers.rb:
SecureHeaders::Configuration.default do |config|
  config.hsts = "max-age=31536000; includeSubdomains"
  config.x_frame_options = "SAMEORIGIN"
  config.x_content_type_options = "nosniff"
  config.x_xss_protection = "1; mode=block"
  config.csp = {
    default_src: %w('self'),
    script_src: %w('self' https: 'unsafe-inline'),
    style_src: %w('self' https: 'unsafe-inline'),
    img_src: %w('self' data:),
    object_src: %w('none')
  }
end
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Regular security scans and dependency audits help you stay ahead of potential vulnerabilities.
  • Automated tools integrated into your CI/CD pipeline can provide continuous feedback on the security posture of your application.

Additional Gems, Libraries, and Tools

Enhance your Rails security posture with these recommended tools:

  • Brakeman:

    A static analysis tool designed specifically for Rails, Brakeman scans your codebase for vulnerabilities without executing your application.

    Official Documentation

  • bundler-audit:

    This gem checks your Gemfile.lock for known vulnerabilities in your dependencies, helping you maintain an up-to-date and secure set of gems.

    Official Documentation

  • secure_headers:

    A gem that simplifies the management of HTTP security headers, reducing risks from clickjacking, MIME-sniffing, and XSS attacks.

    GitHub Repository

  • Pundit:

    A lightweight authorization library that enforces access control on a per-model basis, ensuring that only permitted users can perform specific actions.

    Official Documentation

  • ActiveStorage:

    Rails’ built-in solution for file uploads, designed with security and scalability in mind.

    Rails Guides on ActiveStorage


Security Best Practices Checklist

Before deploying or auditing your Rails application, ensure you have implemented the following:

  • SQL Injection:

    • Use parameterized queries and ActiveRecord methods.
    • Avoid raw SQL concatenation.
  • XSS Mitigation:

    • Rely on Rails’ auto-escaping features.
    • Use sanitization helpers (e.g., sanitize, h) where necessary.
  • Secrets & Credentials Management:

    • Store sensitive data in Rails encrypted credentials or environment variables.
    • Never hard-code secrets in your source code.
  • Session Management:

    • Configure secure session stores (consider ActiveRecord store).
    • Enforce secure, httponly, and same_site cookie flags.
  • CSRF Protection:

    • Enable protect_from_forgery in ApplicationController.
    • Ensure authenticity tokens are present in forms.
  • Secure File Uploads:

    • Use ActiveStorage or Shrine for file handling.
    • Validate file types, sizes, and scan for potential threats.
  • Authentication & Authorization:

    • Implement Devise for authentication.
    • Use Pundit or Cancancan for resource authorization.
  • Continuous Monitoring & Auditing:

    • Integrate Brakeman and bundler-audit into your CI/CD pipeline.
    • Regularly review and update security headers using secure_headers.

Conclusion

Securing your Ruby on Rails application is a continuous, multi-layered process that requires diligence, the right tools, and a proactive mindset. By implementing these best practices—from defending against SQL injection and XSS to managing sessions securely and safeguarding your secrets—you not only protect your users’ data but also enhance your application’s reliability and trustworthiness.

Remember, security isn’t a one-time checkbox; it’s an ongoing commitment. Stay informed of the latest security developments, regularly audit your codebase, and continuously integrate modern security practices into your development workflow. Your future self—and your users—will thank you.


Heroku

This site is built on Heroku

Join the ranks of developers at Salesforce, Airbase, DEV, and more who deploy their mission critical applications on Heroku. Sign up today and launch your first app!

Get Started

Top comments (2)

Collapse
 
gregmolnar profile image
Greg Molnar

There are a quite a few things you are missing and there are some incorrect statements in this article.

Collapse
 
harsh_u115 profile image
Harsh patel

@gregmolnar Sure, if you could highlight those parts, i can rework on that and improve blog and my knowledge.

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay