How do I secure a Rails app?

Direct answer

Rails defaults are strong, CSRF protection, escaped templates, a parameterised ORM. The gaps are strong parameters left permissive, string interpolation inside `where`, and finders that look up records by ID without scoping to the current user. Run Brakeman; it's free and catches most of the first two.

Muhammad HasanUpdated

Scope finders to the user

# Vulnerable
@order = Order.find(params[:id])

# Fixed
@order = current_user.orders.find(params[:id])

The first returns any order to any logged-in user who changes the ID. The second raises RecordNotFound if it isn't theirs, which Rails renders as a 404. The right response, since it doesn't confirm the record exists.

Loading through the association rather than the model is the whole fix. Apply it everywhere. Of the 561 findings in our 24-app study, this class of bug did more damage than any other.

Strong parameters

# Dangerous
params.require(:user).permit!

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

permit! allows everything, including attributes like admin or role that a form never exposed. Permit fields explicitly, and be careful with nested attributes. Permitting an association can pull in more than intended.

SQL

The ORM parameterises. Interpolation doesn't:

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

# Safe
User.where(email: params[:email])
User.where("email = ?", params[:email])

Also watch order, pluck and find_by_sql, which accept raw SQL fragments. Column names in order can't be parameterised, so validate against an allowlist.

Configuration

config.force_ssl = true

Secrets belong in encrypted credentials or the environment, never in the repository. If secret_key_base has ever been committed, rotate it, session cookies are signed with it, so a leaked value means forgeable sessions.

config.consider_all_requests_local must be false in production, or error pages expose stack traces and source.

CSRF

On by default via protect_from_forgery. Two ways it gets weakened: skip_before_action :verify_authenticity_token on controllers handling API requests, and protect_from_forgery with: :null_session where :exception was intended.

If a controller serves both browser and API traffic, the API side needs its own authentication rather than an exemption.

Templates

ERB escapes by default. html_safe and raw turn it off, and anywhere either is applied to user content is potential XSS. sanitize with an allowlist is the safer option when markup is genuinely needed.

Tooling

Brakeman is a Rails-specific static analyser, free, and good at SQL injection, mass assignment and unsafe rendering. bundler-audit covers known CVEs in gems. Both belong in CI.

Neither reliably catches the unscoped finder, because Order.find(params[:id]) is legitimate in plenty of contexts. An admin controller, for example. Distinguishing safe from unsafe needs an understanding of what the controller is for, which is where pattern matching struggles.

Go deeper

Kolega for AppSec teams

Related answers

See what your own repository returns

Connect a repo and run a scan. No credit card, no pipeline changes.

Get started for free