Skip to content
>_ITDITDWeb Security Platform

By framework

Ruby on Rails security — a production hardening reference

A Ruby on Rails production-hardening reference: a priority checklist plus secrets/credentials, config, gem CVEs, Strong Parameters, authorization, injection, and SSRF. Defensive, no attack steps.

Published 2026-07-02 Updated 2026-07-02 6 min read

For: anyone running a Ruby on Rails app. No attack steps here — this is a working reference for hardening: a priority-ordered checklist, per-area guidance, and self-verification. For the cross-framework picture, see the security-by-framework hub.

Priority-ordered hardening checklist

Do this table top-down. P0 is a prerequisite, P1 is the most frequent source of incidents, P2 is ongoing operational hygiene.

P0 ── Prerequisite (do first)

Secrets & credentials management / production config (no exception exposure, force_ssl) / gem CVEs patched fast

P1 ── Top source of incidents

Strong Parameters/Mass Assignment control / authorization (owner scope)

P2 ── Operational hygiene

Injection and dangerous methods / sessions, CSRF / SSRF, uploads

Harden from the foundation up: P0 (prerequisite) → P1 (top source of incidents) → P2 (operational hygiene).
PriorityControlSpecifics (Rails)
P0Secrets & credentialsEncrypted credentials + separate master key. Don't commit the master key. Rotate secret_key_base on leak
P0Production configDon't expose exception detail; config.force_ssl; filter_parameters to keep secrets out of logs
P0gem (dependency) CVEsMonitor with bundler-audit / osv-scanner; judge by running version, patch fast
P1Strong ParametersKeep permit minimal. Don't use permit!. Never assign a privilege field
P1Explicit authorizationPundit / CanCanCan + authorize + current_user scoping for ownership
P2Injection/dangerous methodsBind in where. Don't pass input to send/constantize. Don't load untrusted YAML/Marshal
P2Sessions/CSRFprotect_from_forgery (default), cookie secure/httponly/samesite, regenerate on login
P2SSRF/uploadsAllowlist server-side fetches + block internal IPs. Validate uploads, store outside public/

1. Secrets and credentials (P0)

  • Use Rails's encrypted credentials, and don't commit the master (decryption) key to the repo (inject it via env, etc.).
  • secret_key_base underpins signed/encrypted cookies and sessions. Rotate promptly if leaked (note this invalidates existing signatures/encryption).
  • Don't leave .env, dumps, or backups in a public directory (→ keep secrets out of public directories).

2. Production config (P0)

  • In production, don't expose exception detail (don't enable consider_all_requests_local, etc.) — reduce internal-structure disclosure.
  • config.force_ssl to force HTTPS and mark cookies secure. Don't enable dev tools like the web console in production.
  • Use filter_parameters to keep passwords and the like out of logs.

3. gem (dependency) CVEs (P0)

4. Strong Parameters and Mass Assignment (P1)

  • Keep permit fields to the minimum, and avoid permit! (allow-all).
  • Never assign a privilege field like is_admin from user input. Drop "permit broadly because it's convenient."

5. Authorization (P1 — the top source)

Common (dangerous)

  • no authorization — "logged in = can view/update"
  • route-model binding fetches another user's ID
  • relying on hidden routes / hard-to-guess IDs
  • forgetting authorize on some actions

Correct

  • policy explicit with Pundit / CanCanCan + authorize per action
  • reads too are owner-scoped by current_user
  • verified on every read/update/delete path
  • authorization built explicitly, not left to defaults

See what IDOR is. Authentication and authorization differ; authorize close to the data.

6. Injection and dangerous dynamic methods (P2)

  • SQL: bind with placeholders in where; never build queries by string concatenation (avoid where("... #{params}")) (→ what SQL injection is).
  • Dangerous dynamic methods: don't pass user input to send/public_send/constantize. If you must, restrict strictly with an allowlist.
  • Deserialization: avoid loading untrusted YAML/Marshal (can lead to code execution under the right conditions).

7. Sessions, cookies, CSRF (P2)

  • Don't globally disable CSRF protection (protect_from_forgery); use the form token (→ what CSRF is).
  • Set secure/httponly/samesite on cookies/sessions, and regenerate the session on login.

8. SSRF, uploads, headers (P2)

  • Server-side fetching of user-supplied URLs should restrict targets to an allowlist and block reaching internal IPs/metadata (→ what SSRF is).
  • Validate uploads (type/size), store outside public/, and grant no execute permission.
  • Add security headers (check your own site with the security headers checker).

Verify: is your Rails actually hardened?

Building it isn't the end — it's done only once you've checked. These are defensive self-checks against your own environment.

1

Secrets aren't exposed/committed

Confirm master.key isn't in the repo, and that .env/dumps aren't fetchable by URL.
2

No exception detail in production

Trigger an error and confirm detailed exceptions aren't shown externally, and that HTTPS is forced.
3

Authorization holds

In a test environment, request another user's resource ID and confirm it is denied (read/update/delete).
4

Dependencies and headers

Confirm bundler-audit/osv is clean, and that HTTPS/HSTS are present via the headers checker.

This site's view: even guarded by conventions, authorization and dependencies are on you

Rails's good defaults erase a lot of risk, but authorization — who may do what — and dependency freshness are app- and operation-specific, so a framework can't guard them automatically. The incidents we keep seeing are less elaborate attacks than the "authenticated but no ownership check" type. So the center of gravity is working the table above top-down — tighten Strong Parameters, make authorization explicit, and monitor gems for CVEs.

FAQ

QWhat should I do first to secure Rails?
A

The three P0 items: (1) handle secrets safely (keep encrypted credentials separate from the master key, don't commit the master key; secret_key_base underpins signed/encrypted cookies, so rotate on leak); (2) firm up production config (don't expose exception detail; force_ssl); (3) machine-monitor gem (dependency) CVEs and patch fast, judging by the running version. Next, move to Strong Parameters and authorization.

QWhat should I watch for with Strong Parameters?
A

Explicitly allow (permit) which fields you accept to prevent Mass Assignment (bulk-assigning unintended fields). The danger is permitting broadly for convenience, and using permit! to allow everything. Keep permitted fields to the minimum, and never let a privilege field like is_admin be assigned from user input.

QHow do I implement authorization safely?
A

On top of login (authentication), implement an ownership check that the target truly belongs to the user. Make the policy explicit with Pundit/CanCanCan, run authorize in each action, and scope resources by current_user. Don't rely on hidden routes or hard-to-guess IDs — verify explicitly on every read/update/delete path.

QHow do I manage gem (dependency) vulnerabilities?
A

Machine-monitor known CVEs with bundler-audit or osv-scanner and patch fast, judging by the running version (decide on the actual version in Gemfile.lock). Keep Rails and Ruby on supported versions and don't leave EOL versions in place. Dependency freshness is a more realistic factor in incidents than elaborate attacks.

QWhat are the dangerous dynamic methods?
A

Passing user input to send / public_send / constantize can lead to unintended method calls or class resolution. Likewise, loading via Marshal or untrusted YAML (restoring objects) can, under the right conditions, lead to code execution. Don't pass user-derived data to these; if you must, restrict strictly with an allowlist.