Fix cards for Ruby on Rails
A01:2025 Broken Access Control
- Cross-site request forgery (CSRF)CWE-352
Keep the protection ActionController::Base already has (load_defaults 5.2 adds protect_from_forgery with: :exception): forms from form_with carry the token, fetch() sends csrf_meta_tags' token in X-CSRF-Token, and Rails also checks Origin.
- Missing object-level authorization (IDOR and BOLA)CWE-862 · CWE-639 · CWE-863
Load records through a scope built from the signed-in user, never by ID alone: visible_invoices(current_user).find(params[:id]), where the scope applies the tenant to everyone and the owner to members.
- Open redirectCWE-601
Keep Rails' own check on: redirect_to url_from(params[:next]) || account_path, with other trusted hosts in config.action_controller.allowed_redirect_hosts. url_from returns the value only for a path on this site or an allowed host, and redirect_to raises for anything else.
- Path traversalCWE-22 · CWE-23 · CWE-36 · CWE-59
Resolve the requested path with File.realpath(File.join(base, requested)), which collapses ../ and follows symlinks, and serve it only if it starts with the realpath of the base plus a separator.
- Server-side request forgery (SSRF)CWE-918
Attach the policy to the connection, not the host name string.
A05:2025 Injection
- Cross-site scripting (XSS)CWE-79
Let ERB escape: <%= @name %> and tag helpers such as image_tag(..., alt: @name) escape & < > " and ', unless the value is html_safe.
- CSV formula injectionCWE-1236 · CWE-74
Before writing a user-controlled value to a cell, prefix it with a single quote when it begins with any formula trigger: =, +, -, @, tab (0x09) or carriage return (0x0D).
- SQL injectionCWE-89
Pass values to Active Record as hash conditions or placeholders: where(customers: { name: name }, status: status) or where("customers.name = ?", name).
A06:2025 Insecure Design
- Unrestricted file uploadCWE-434 · CWE-646 · CWE-73 · CWE-770
Decide the type from the bytes, and never serve uploads from the web root.
A07:2025 Authentication Failures
- Missing authentication for a critical functionCWE-306 · CWE-287 · CWE-288 · CWE-290
Keep the before_action :authenticate in ApplicationController so every action needs it, and skip_before_action only on the public health route.
- Session fixation and session cookiesCWE-384 · CWE-613 · CWE-614 · CWE-1004 · CWE-1275
Call reset_session at sign-in, not session.clear: only reset_session issues a new session ID.
A08:2025 Software or Data Integrity Failures
- Insecure deserializationCWE-502
Do not rebuild objects from the token: Base64-decode it and JSON.parse the bytes, then copy only the fields you expect into a fixed shape; Marshal.load runs code chosen by the bytes.
- Mass assignment (over-posting)CWE-915
Pass only an allowlist to the model: user.update(params.permit(:display_name, :bio)), or params.expect(user: [:display_name, :bio]) for a nested body.
Not covered in Rails yet
These weaknesses have fix cards in other stacks but not in this one.
- CORS misconfiguration on credentialed APIs
- XML external entity (XXE) processing
- Insecure randomness for security tokens
- Weak password hashing
- Code injection
- LDAP filter injection
- NoSQL operator injection
- OS command and argument injection
- Server-side template injection
- XPath injection
- Clickjacking
- Regular-expression denial of service (ReDoS)
- Unbounded resource consumption
- Hard-coded secrets
- Improper TLS certificate validation
- JWT validation in APIs (bearer tokens)
- CRLF injection in logs and response headers
Supported versions
Each minor release series gets bug fixes for one year and security fixes for two years after its first release; a new minor release is planned about every six months, and if none ships within a year the previous series stays supported until one does. Unsupported series get no new releases. Rails support policy.
| Version | Latest | Status | Released | End of support |
|---|---|---|---|---|
| 8.1 | 8.1.4 | Supportedrubyonrails.org lists bug fixes until 10 October 2026 and security fixes until 10 October 2027. Rails 8.2 had not been released on 11 October 2026, and the policy extends the previous release's support when no new feature release ships within a year. The cards use 8.1.4. | 22 October 2025 | 10 October 2027 |
| 8.0 | 8.0.5.1 | Security fixes onlyBug fixes ended on 7 May 2026; security fixes end on 7 November 2026. | 7 November 2024 | 7 November 2026 |
| 7.2 | 7.2.4 | End of lifeSecurity support ended on 9 August 2026, two years after 7.2.0 under the maintenance policy. 7.2.3.2 (29 July 2026) was the last 7.2 security release, and 7.2.4 (24 September 2026), a bug-fix release, the last 7.2 release; fixes now go to 8.0 and 8.1 only. | 9 August 2024 | 9 August 2026 |
| Version | Latest | Status | Released | End of support |
|---|---|---|---|---|
| 4.0 | 4.0.7 | Supported | 25 December 2025 | No date set |
| 3.4 | 3.4.11 | Supported | 25 December 2024 | No date set |
| 3.3 | 3.3.12 | Security fixes only | 25 December 2023 | 31 March 2027 |
| 3.2 | 3.2.11 | End of life | 25 December 2022 | 1 April 2026 |
ruby-lang.org lists each branch as in normal maintenance (bug and security fixes), security maintenance (security fixes only) or end of life, with its expected end date. Rails 8.1 needs Ruby 3.2.0 or later, and Ruby 3.2 reached its end of life on 1 April 2026. Ruby release schedule
Minimum secure versions
The lowest release in each supported line that fixes every published advisory listed here. Anything older is missing a security fix.
| Package | At least | Why |
|---|---|---|
rails | 8.1.3.1 or 8.0.5.1 (7.2.3.2 on the unsupported 7.2 line) | CVE-2026-66066 (critical): with libvips image variants and untrusted uploads, Active Storage can be made to read any file, secret_key_base included. Also run libvips 8.13 or later, and rotate secrets if exposed. Advisory |
rails | 8.1.2.1, 8.0.4.1 or 7.2.3.1 | Ten advisories of 23 March 2026, including XSS in Action View tag helpers and SafeBuffer#% (CVE-2026-33168, CVE-2026-33170) and path traversal in Active Storage's DiskService (CVE-2026-33195). Advisory |
ruby | 4.0.7, 3.4.11, or 3.3.12 with the resolv gem 0.3.2 | resolv CVE-2026-80212 and CVE-2026-80213 (27 August 2026): 4.0.7 and 3.4.11 bundle the fixed resolv 0.7.2; 3.3.12 still bundles 0.3.1, so update that gem. Advisory |
Middleware and security order
ActionDispatch::HostAuthorization: First in the stack; it checks the Host header againstconfig.hosts, which is empty, so off, outside development until you set it.ActionDispatch::SSL: Added byconfig.force_ssl = true: redirects HTTP to HTTPS, sends HSTS and marks cookies Secure. Setconfig.assume_ssl = truetoo when a proxy terminates TLS.ActionDispatch::Cookies, the session store,ActionDispatch::Flash: Cookies are signed or encrypted withsecret_key_base; the session store (cookie store by default) comes after them.ActionDispatch::ContentSecurityPolicy::Middleware: Sends the policy fromconfig.content_security_policy, which a new app leaves commented out.- Controller callbacks:
protect_from_forgery with: :exception(added toActionController::Baseby load_defaults 5.2 and later), then authentication inbefore_action, then strong parameters and queries scoped tocurrent_userin the action.
Secure defaults and settings
| Setting | Value | Why |
|---|---|---|
config.force_ssl, config.assume_ssl | Commented out in a new app's production.rb (set when the app skips Kamal) | Set force_ssl = true unless a proxy you control redirects to HTTPS, sends HSTS and keeps cookies Secure. |
config.hosts | Empty outside development, so no Host header checks | List your host names. |
config.content_security_policy | Not set (the generated initializer is commented out) | Define a policy; content_security_policy_nonce_generator adds nonces. |
config.action_controller.action_on_open_redirect, action_on_path_relative_redirect | :raise (load_defaults 7.0 and 8.1) | Keep them; list other trusted hosts in allowed_redirect_hosts. |
config.action_controller.default_protect_from_forgery, forgery_protection_origin_check | true, true (load_defaults 5.2 and 5.0) | Keep them. |
config.action_dispatch.cookies_same_site_protection | :lax (load_defaults 6.1) | Keep it. |
config.active_support.message_serializer | :json_allow_marshal (load_defaults 7.1) | Switch to :json once no Marshal-serialized messages remain; the Rails guide warns that Marshal is a deserialization vector if a signing secret leaks. |
config.action_controller.action_on_unpermitted_parameters | :log in development and test, false otherwise | Set :raise in development and test. |
config.session_store | Cookie store, _<app>_session | Store a sign-in time and enforce an absolute timeout, or use a server-side store so sessions can be ended; call reset_session at sign-in. |
Escape hatches to search for
Framework APIs that switch a protection off. Each one is sometimes needed; search for them in review and make each use justify itself.
| API | Risk | Instead |
|---|---|---|
raw, html_safe, <%== | Turns off HTML escaping. | <%= %> and tag helpers; sanitize with an allowlist for rich text. |
skip_forgery_protection, skip_before_action :verify_authenticity_token | Removes CSRF protection, also for requests that carry the session cookie. | Keep it; token-authenticated APIs that never read the session cookie can use ActionController::API. |
String interpolation in where, order, find_by_sql, Arel.sql | SQL injection. | Hash conditions, ? placeholders and allowlisted Arel attributes for sorting. |
permit!, to_unsafe_h | Mass assignment of every attribute. | permit or expect with the keys users may set. |
redirect_to params[...], allow_other_host: true | Open redirect. | url_from(params[...]) || fallback and allowed_redirect_hosts. |
Rails.root.join(params[...]), send_file params[...] | Path traversal; an absolute path replaces the base. | Look files up by ID, or check File.realpath against the base directory. |
Model.find(params[:id]) | IDOR for every signed-in user. | Find through a scope built from current_user. |
Checklist, mapped to ASVS 5.0.0
- v5.0.0-1.2.1 Views rely on ERB escaping; no request data reaches
raw,html_safeor<%==. - v5.0.0-3.5.1 Forgery protection is on and no controller that reads the session cookie skips it.
- v5.0.0-8.2.2 Actions load records through scopes built from
current_user. - v5.0.0-1.2.4 No SQL fragment is built by string interpolation.
- v5.0.0-15.3.3 Controllers permit explicit keys; no
permit!orto_unsafe_hreaches a model. - v5.0.0-3.7.2 Redirects to request data go through
url_from, and noallow_other_host: truetakes request data. - v5.0.0-7.2.4 Sign-in calls
reset_session. - v5.0.0-3.4.1
config.force_ssl(or the proxy) sends HSTS for at least a year. - v5.0.0-3.3.1 Session and other cookies are Secure.
- v5.0.0-13.4.2
consider_all_requests_localis false in production, so errors show the generic page.
References
- Rails: maintenance policy and supported releases
- Ruby: maintenance branches
- Rails Guides: Securing Rails Applications
- Rails Guides: configuring Rails applications (middleware and defaults)
- Rails Guides: Rails on Rack, the internal middleware stack
- GHSA-xr9x-r78c-5hrm: arbitrary file read in Active Storage variant processing (CVE-2026-66066)
- Rails Versions 7.2.3.1, 8.0.4.1 and 8.1.2.1 have been released (ten advisories)
- Rails Version 7.2.4 has been released (the last 7.2 release)