Skip to main content
Back to blog
April 28, 2026|8 min read

Ruby on Rails Security Guide 2026: CSRF, Strong Params, Brakeman & CSP

Rails security is famously opinionated — but even the most secure defaults can be misconfigured. This guide covers every layer from strong parameters to Brakeman and the Rails CSP DSL.

ZeriFlow Team

992 words

Ruby on Rails Security Guide 2026: CSRF, Strong Params, Brakeman & CSP

Rails security has always been a core framework value. The Rails team ships protections for SQL injection, XSS, CSRF, and session hijacking out of the box. But 'out of the box' is only safe if you know what you have — and what you can accidentally turn off.

Start with a live scan of your deployed application at ZeriFlow — 80+ checks including headers, TLS, and exposed files, free and instant.


1. CSRF Protection in Rails

Rails includes CSRF protection in every non-API controller via protect_from_forgery. It is enabled by default in ApplicationController.

ruby
class ApplicationController < ActionController::Base
  # Default for full-stack Rails — verified by origin AND token
  protect_from_forgery with: :exception

  # For API-only applications using cookie sessions
  # protect_from_forgery with: :null_session
end

In your layout, the csrf_meta_tags helper embeds the token for JavaScript clients:

erb
<head>
  <%= csrf_meta_tags %>
</head>

For Rails API mode with JWT in Authorization headers, CSRF is not applicable because the token is not sent automatically by the browser. Do not re-enable cookie auth in API-only apps without also re-enabling CSRF protection.

Never do this in a full-stack app:

ruby
skip_before_action :verify_authenticity_token # Disables CSRF — only for APIs

2. Strong Parameters

Strong parameters prevent mass assignment vulnerabilities by requiring explicit whitelisting of permitted attributes.

ruby
class UsersController < ApplicationController
  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to @user
    else
      render :new, status: :unprocessable_entity
    end
  end

  private

  def user_params
    params.require(:user).permit(:name, :email, :password, :password_confirmation)
    # Never use params.require(:user).permit! — this allows ALL attributes
  end
end

For nested attributes:

ruby
def article_params
  params.require(:article).permit(
    :title, :body, :published,
    tags_attributes: [:id, :name, :_destroy]
  )
end

Strong parameters also protect against injecting admin: true or role: 'superuser' through a form.


3. Brakeman: Static Security Analysis

Brakeman is a static analysis tool that scans your Rails application for security vulnerabilities. It should run in your CI pipeline on every pull request.

bash
gem install brakeman
brakeman -q -w2 --no-pager

Common findings Brakeman catches: - SQL injection in where() with string interpolation - render calls with user-controlled template names - Redirect to user-supplied URLs - eval or send with user input - Missing CSRF protection

Add to CI (GitHub Actions):

yaml
- name: Run Brakeman
  run: |
    gem install brakeman
    brakeman -q --exit-on-warn

--exit-on-warn fails the build on any warning — treat security warnings as build failures.


4. Content Security Policy DSL

Rails 6+ includes a built-in Ruby DSL for setting Content Security Policy headers.

ruby
# config/initializers/content_security_policy.rb
Rails.application.configure do
  config.content_security_policy do |policy|
    policy.default_src :self
    policy.font_src    :self, 'https://fonts.gstatic.com'
    policy.img_src     :self, :data, 'https:'
    policy.object_src  :none
    policy.script_src  :self
    policy.style_src   :self, 'https://fonts.googleapis.com'
    policy.connect_src :self

    # Specify URI for violation reports
    policy.report_uri '/csp-violation-report'
  end

  # Enable nonce for inline scripts (use with Rails UJS or Stimulus)
  config.content_security_policy_nonce_generator = ->(_request) {
    SecureRandom.base64(16)
  }
  config.content_security_policy_nonce_directives = %w[script-src]

  # Report-only mode for testing
  # config.content_security_policy_report_only = true
end

Use content_security_policy at the controller level to override per-action:

ruby
class InlineEditorController < ApplicationController
  content_security_policy do |p|
    p.style_src :self, :unsafe_inline
  end
end

5. Force SSL and Secure Cookies

In config/environments/production.rb:

ruby
Rails.application.configure do
  # Force HTTPS for all requests
  config.force_ssl = true

  # Session cookie configuration
  config.session_store :cookie_store,
    key:      '__Host-session',
    secure:   true,
    httponly: true,
    same_site: :lax,
    expire_after: 30.minutes
end

The __Host- prefix on the cookie name enforces additional browser security: - The Secure flag is required - The Domain attribute must be absent - The Path attribute must be /

This prevents subdomain cookie injection attacks.


6. Bundler Audit: Dependency Vulnerability Scanning

bash
gem install bundler-audit
bundle-audit update  # Update vulnerability database
bundle-audit check

Add to CI:

yaml
- name: Bundle Audit
  run: |
    gem install bundler-audit
    bundle-audit update
    bundle-audit check --ignore CVE-XXXX-XXXX # Only ignore with justification

Also consider ruby_audit for Ruby interpreter vulnerabilities:

bash
gem install ruby_audit
ruby-audit check

Combine code-level auditing with a live scan — ZeriFlow tests your deployed application for header misconfiguration, TLS issues, and exposed paths that static analysis cannot see.


FAQ

### Q: Does Rails protect against SQL injection automatically? A: Yes — ActiveRecord uses parameterized queries. The risk arises when using string interpolation in where() or find_by_sql(). Always pass conditions as hashes or arrays: User.where(email: params[:email]) or User.where('email = ?', params[:email]).

### Q: What is the safest way to handle file uploads in Rails? A: Use Active Storage with cloud storage (S3 or GCS). Validate content type using content_type_must_be_a_pdf or a custom validator, set a maximum file size, and never trust the content type sent by the browser. Use Marcel for server-side MIME detection.

### Q: Is Rails' default XSS protection sufficient? A: Rails auto-escapes all ERB output (<%= ... %>) which prevents most reflected XSS. The risk comes from html_safe, raw(), and sanitize() with permissive options. Use sanitize() only with a strict allowlist and avoid html_safe on user-controlled content.

### Q: How do I secure Rails API tokens? A: Use has_secure_token for simple API tokens. Store a digest of the token in the database using has_secure_token :api_key, format: :base58. For OAuth-based flows, use Doorkeeper.

### Q: Should I upgrade to the latest Rails minor version immediately? A: Yes for security patches — Rails follows semantic versioning and security fixes are backported to supported minor versions. Subscribe to the rubyonrails-security mailing list and upgrade within 72 hours of a security release.


Conclusion

Rails security in 2026 is a mature discipline. The framework provides CSRF protection, strong parameters, auto-escaping XSS protection, and secure defaults for sessions. Your job is to avoid disabling them by accident, run Brakeman in CI, keep Bundler audit green, and enforce HTTPS and CSP in production.

The final check is always from the outside. Run a free ZeriFlow scan after every deploy to verify your headers, TLS configuration, and public exposure — the same view your attackers have.

Ready to check your site?

Run a free security scan in 30 seconds.

Related articles

Keep reading