Skip to content
Security & Trust

Rails 8.1 apps can log a client-chosen IP when proxies pass Forwarded through

A refactor in Rails 8.1.0 changed which header the app believes. The proxy in front may not know.

W
WebPulse Newsroom
AI-assisted · 4 min read
Share on X LinkedIn
Rails 8.1 apps can log a client-chosen IP when proxies pass Forwarded through
In brief
  • The Rails team says Rails 8.1.0 through 8.1.4 on Rack 3 can record a client-chosen IP address when a proxy passes a client-supplied Forwarded header through.
  • This can affect IP-based access rules, rate limits and audit logs that rely on request.remote_ip.
  • Check your Rails and Rack versions, make proxies strip or overwrite Forwarded, and test the upgrade because the fix can raise errors.

A security control is only as sound as the agreement behind it. Two systems can each follow their own rules and still disagree about who is knocking. A Rails advisory shows what that disagreement can cost.

What the Rails team reported

The Rails project published advisory GHSA-2gff-2mmg-m73p. It says that since Rails 8.1.0, the framework works out the visitor's address, called request.remote_ip, through Rack. Rack is the layer between Ruby web servers and Rails. Under Rack 3, it reads the standard Forwarded header in preference to the older X-Forwarded-For header.

The advisory describes the risk this way. A reverse proxy writes the real client address into X-Forwarded-For. But a Forwarded header typed by the visitor travels on to the app exactly as it arrived. Rails then returns an address the client chose.

The Rails team says an unauthenticated attacker could use this to bypass IP-based access controls and rate limits. That includes the default setting of Rails' rate_limit feature. It could also falsify the client address in logs. The team says "possibly" about the bypass, so treat it as a risk to test, not a certainty.

How the trick works

A reverse proxy sits in front of your application. It receives the visitor's request and adds headers saying where it came from. Many proxies are built around X-Forwarded-For and have no rule for Forwarded.

A security blogger (lkoskela.fi) describes the same pattern in a general post on Forwarded-header trust. The post says most proxies, content delivery networks and similar middleware let an incoming Forwarded header pass. So a header an attacker sends can reach the app untouched. The post does not discuss this Rails advisory.

The post gives an example with two addresses. In its diagram, the proxy records the real source, 1.2.3.4, in X-Forwarded-For. The attacker's Forwarded header claims 127.0.0.1 instead. An app that prefers Forwarded stores the false value.

Rails has a built-in spoofing check. The advisory says it did not fire here, because it compares only the Client-Ip header with the forwarded addresses.

One convenience change moved the trust line

The advisory says the 8.1.0 change was made to reuse Rack's port handling. It was a tidy engineering decision. The side effect was that the app began trusting a header the proxy had not been asked to police.

This shows how trust boundaries fail. They are rarely broken in one place. They drift when one side changes what it reads and the other side does not know.

The advisory sets three conditions, and all must hold. The app runs a Rails release from 8.1.0 to 8.1.4 on Rack 3. Under Rack 2.2, the advisory says forwarded_for reads only X-Forwarded-For. Its proxy leaves a visitor-written Forwarded header in place. And its code trusts request.remote_ip to grant access, limit traffic or keep audit records.

The blog post adds that Spring and Jetty also read Forwarded in their default configuration, alongside Rails. It says the same weakness applies to the host and protocol fields. It names password reset email deception through host name overrides as a typical example of the harm.

WebPulse's September 2026 scan of the Tranco top 10,000 domains detected Rails on 85 sites. That is 3.4% of the 2,491 sites where WebPulse detected a platform. The scan cannot see Rails versions, Rack versions or proxy settings. It does not show how many sites are affected.

The fix has a catch of its own

The patched releases add a cross-check. With the default ip_spoofing_check setting on, Rails takes the first client address it does not trust from each header. It compares the two as exact strings. If both headers are present and the addresses differ, Rails raises ActionDispatch::RemoteIp::IpSpoofAttackError.

That has a cost. The advisory says any setup where proxies describe the visitor differently in the two headers will hit this error once upgraded.

The fix also has a limit. It can only flag a fake Forwarded header when the proxy fills in X-Forwarded-For as well. If your proxy lets Forwarded through and sets no X-Forwarded-For, the advisory says the app remains exposed after upgrading.

For teams that cannot upgrade at once, the advisory offers a workaround. If your proxies set X-Forwarded-For, set Rack::Request.forwarded_priority to [:x_forwarded] in an initializer. That restores the Rails 8.0 behaviour. It also makes Rack ignore Forwarded for scheme, host and port.

Questions to put to your team

Ask for a list of production apps on Rack 3 that run any Rails release from 8.1.0 to 8.1.4. Ask who owns the proxy configuration, and whether it removes or overwrites Forwarded on incoming requests. Ask which controls depend on request.remote_ip: allow-lists, rate limits, fraud rules or audit trails.

Then ask for a test, not an assurance. Send a request with a made-up Forwarded header through your real proxy path and see what the app logs. Plan the upgrade with staging, so a new spoof error does not surprise customers.

The lesson is that a header is a claim, not a fact. Someone must decide, in writing, which systems may make that claim.

Produced by the WebPulse Newsroom with AI assistance from the original reporting credited below, and checked against that source by our editorial review. How we use AI.
Original reporting: Rails project (rails/rails security advisory GHSA-2gff-2mmg-m73p).

Rails vs Spring