{"id":"GHSA-2gff-2mmg-m73p","kind":"vulnerability","aliases":["GHSA-2gff-2mmg-m73p"],"related":[],"title":"Possible IP spoofing in Action Dispatch RemoteIp","description":"### Impact\nSince Rails 8.1.0, `ActionDispatch::RemoteIp` derives `request.remote_ip` from `Rack::Request#forwarded_for`, which under Rack 3 reads the RFC 7239 `Forwarded` header in preference to `X-Forwarded-For`. When a reverse proxy appends the client address to `X-Forwarded-For` but passes a client-supplied `Forwarded` header through unchanged, `request.remote_ip` returns an address chosen by the client, and the IP spoofing check does not raise, because it compares only `Client-Ip` with the forwarded addresses. An unauthenticated attacker could therefore spoof `request.remote_ip`, possibly bypassing IP-based access controls and rate limits keyed on the client address (including the default `by:` of `rate_limit`), and falsifying the client address in logs.\n\n### Details\nThe 8.1.0 change ([2d084af8](https://github.com/rails/rails/commit/2d084af8444a7b6b868c7cbb256172007871e78d)) switched `ActionDispatch::RemoteIp` from reading `X-Forwarded-For` directly to Rack's `forwarded_for`, in order to reuse Rack's port handling. Rails continues to honor `Forwarded`, the standard header.\n\nIn the fixed releases, when `config.action_dispatch.ip_spoofing_check` is enabled (the default), `ActionDispatch::RemoteIp` also compares the first untrusted client address in `Forwarded` with the first untrusted client address in `X-Forwarded-For`, as exact strings, and raises `ActionDispatch::RemoteIp::IpSpoofAttackError` when both headers are present and the addresses differ. Deployments whose proxies report the client address inconsistently between the two headers will receive this error after upgrading. See the `actionpack` CHANGELOG.\n\n### Affected applications\nAn application is affected if it meets all of these requirements:\n- Runs Rails 8.1.0 through 8.1.4 with Rack 3. Under Rack 2.2, `forwarded_for` reads only `X-Forwarded-For`.\n- Is served behind a proxy that does not remove or overwrite a client-supplied `Forwarded` header.\n- Relies on `request.remote_ip` for access control, rate limiting, or auditing.\n\nThe fix detects a spoofed `Forwarded` header only when the proxy also sets `X-Forwarded-For`. An application whose proxy passes a client-supplied `Forwarded` header through and sets no `X-Forwarded-For` remains exposed after upgrading, and should configure the proxy to remove or overwrite `Forwarded`, or apply the workaround below.\n\n### Mitigation\n- Upgrade to a fixed version of `actionpack`.\n- Configure reverse proxies to remove or overwrite any client-supplied `Forwarded` header.\n\n### Workarounds\nApplications whose proxies set `X-Forwarded-For` can make Rack ignore the `Forwarded` header, restoring the client address resolution of Rails 8.0, by setting `Rack::Request.forwarded_priority = [:x_forwarded]` in an initializer. This setting is available in Rack 3.0.0 and later, and it also causes Rack to ignore `Forwarded` when determining the request scheme, host, and port.\n\n### Releases\nThe fixed releases are available at the normal locations.\n\nPatches for those who cannot upgrade at once:\n- 8.1: https://github.com/rails/rails/commit/2a01a5ac521184419d9a92fa2c25e102e68cd227.patch\n\n### Versions affected\n- actionpack >= 8.1, < 8.1.4.1\n\n### Credit\nThis issue was responsibly reported by HackerOne researcher [luryus](https://hackerone.com/luryus).","summary":"Rails 8.1.0 through 8.1.4 reads the client IP from the Forwarded header ahead of X-Forwarded-For when Rack 3 is in use. An unauthenticated attacker who can send a Forwarded header through a proxy that passes it on can set request.remote_ip to any address, which can bypass IP-based access control and rate limits and falsify audit logs.","severity":"high","cvss":7.4,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"actionpack","fixed":["8.1.4.1"],"affected":">= 8.1, < 8.1.4.1","ecosystem":"rubygems"}],"ecosystems":["rubygems"],"tags":["actionpack","cwe-348","package","rails","rate-limiting","ruby","self-hosted"],"not_affected_if":["default-proxy-strips-forwarded","not-using-remote-ip","rack-2"],"how_to_check":"- Check the Rails version in `Gemfile.lock` for `actionpack` between `8.1.0` and `8.1.4`, and confirm Rack 3 is in use (`rack` 3.x in `Gemfile.lock`).\n- Check whether the reverse proxy in front of the app removes or overwrites a client-supplied `Forwarded` header.\n- Check whether the app relies on `request.remote_ip` for access control, rate limiting, or auditing.","fix":"- `actionpack` `8.1.4.1`\n- Configure reverse proxies to remove or overwrite any client-supplied `Forwarded` header.\n- Workaround for proxies that set `X-Forwarded-For`: set `Rack::Request.forwarded_priority = [:x_forwarded]` in an initializer (Rack 3.0.0 and later; this also makes Rack ignore `Forwarded` for scheme, host and port).","status":"open","published_at":"2026-11-04T03:01:54+00:00","first_seen_at":"2026-11-09T22:53:29+00:00","triaged_at":"2026-11-09T22:54:29+00:00","updated_at":"2026-11-09T22:54:29+00:00","category":"package","confidence":0.85,"triage":{"at":"2026-11-09T22:54:29Z","model":"claude-haiku-5-5","output":{"fix":"- `actionpack` `8.1.4.1`\n- Configure reverse proxies to remove or overwrite any client-supplied `Forwarded` header.\n- Workaround for proxies that set `X-Forwarded-For`: set `Rack::Request.forwarded_priority = [:x_forwarded]` in an initializer (Rack 3.0.0 and later; this also makes Rack ignore `Forwarded` for scheme, host and port).","reach":"internet","title":"Rails: forged Forwarded header lets clients spoof their IP address","impact":"medium","summary":"Rails 8.1.0 through 8.1.4 reads the client IP from the Forwarded header ahead of X-Forwarded-For when Rack 3 is in use. An unauthenticated attacker who can send a Forwarded header through a proxy that passes it on can set request.remote_ip to any address, which can bypass IP-based access control and rate limits and falsify audit logs.","audience":"developers","category":"package","nicknames":[],"who_cares":["rails","actionpack","ruby","self-hosted","rate-limiting"],"confidence":0.85,"fix_status":"available","needs_auth":"no","worst_case":"Client falsifies its IP, bypassing IP-based access controls and rate limits","analyst_note":"","how_to_check":"- Check the Rails version in `Gemfile.lock` for `actionpack` between `8.1.0` and `8.1.4`, and confirm Rack 3 is in use (`rack` 3.x in `Gemfile.lock`).\n- Check whether the reverse proxy in front of the app removes or overwrites a client-supplied `Forwarded` header.\n- Check whether the app relies on `request.remote_ip` for access control, rate limiting, or auditing.","plain_action":"","product_name":"","needs_analyst":false,"not_affected_if":["rack-2","default-proxy-strips-forwarded","not-using-remote-ip"],"who_is_affected":"Applications running Rails 8.1.0 through 8.1.4 with Rack 3, served behind a proxy that passes a client-supplied Forwarded header through unchanged, and that use request.remote_ip for access control, rate limiting or auditing. The fix detects a spoofed Forwarded header only when the proxy also sets X-Forwarded-For, so an app whose proxy sets no X-Forwarded-For stays exposed after upgrading unless the proxy strips Forwarded. Apps on Rack 2.2 or on Rails 8.0 are not affected.","user_interaction":"no","product_ecosystem":"","needs_special_setup":"yes","severity_if_unknown":"high"},"cost_usd":0.001035,"input_tokens":5805,"output_tokens":910,"severity_before":"high"},"analyst_status":null,"analyst_note":null,"search":"'-348':21B '/luryus).':558C '/rails/rails/commit/2a01a5ac521184419d9a92fa2c25e102e68cd227.patch':540C '/rails/rails/commit/2d084af8444a7b6b868c7cbb256172007871e78d))':219C '2.2':347C '2d084af8':216C '2gff':10A,15A '2mmg':11A,16A '3':51B,110C,344C '3.0.0':500C '7239':114C '8.0':483C '8.1':537C,544C '8.1.0':32B,96C,214C,339C '8.1.4':34B,341C '8.1.4.1':545C 'access':84B,188C,378C 'action':5A 'actiondispatch':97C,221C,263C,290C 'actionpack':18A,19B,322C,446C,543C 'address':77B,131C,151C,174C,197C,209C,271C,279C,300C,308C,479C 'affect':324C,329C,542C 'ahead':43B 'also':265C,395C,505C 'append':128C 'appli':435C 'applic':325C,327C,402C,461C 'attack':57B,177C 'audit':91B,383C 'avail':497C,524C 'base':83B,187C 'behind':358C 'bypass':80B,184C 'cannot':533C 'caus':506C 'chang':215C 'changelog':323C 'check':160C,258C 'chosen':152C 'client':37B,130C,141C,155C,169C,196C,208C,270C,278C,307C,369C,408C,456C,478C 'client-ip':168C 'client-suppli':140C,368C,407C,455C 'compar':166C,266C 'config.action_dispatch.ip':256C 'configur':426C,447C 'continu':244C 'control':85B,189C,379C 'could':178C 'credit':546C 'cwe':20B 'default':200C,262C 'deploy':302C 'deriv':99C 'detail':212C 'detect':386C 'determin':512C 'differ':301C 'direct':229C 'dispatch':6A 'enabl':260C 'error':317C 'exact':286C 'expos':421C 'falsifi':90B,206C 'first':268C,276C 'fix':253C,385C,443C,521C 'forward':41B,47B,62B,105C,115C,122C,135C,143C,173C,227C,233C,247C,273C,283C,348C,354C,371C,389C,399C,410C,418C,433C,458C,467C,474C,490C,510C 'ghsa':9A,14A 'ghsa-2gff-2mmg-m73p':8A,13A 'github.com':218C,539C 'github.com/rails/rails/commit/2a01a5ac521184419d9a92fa2c25e102e68cd227.patch':538C 'github.com/rails/rails/commit/2d084af8444a7b6b868c7cbb256172007871e78d))':217C 'hackeron':553C 'hackerone.com':557C 'hackerone.com/luryus).':556C 'handl':242C 'header':42B,63B,116C,144C,250C,295C,313C,372C,390C,411C,459C,475C 'honor':246C 'host':30B,516C 'ignor':472C,509C 'impact':93C 'includ':198C 'inconsist':309C 'initi':493C 'ip':2A,38B,74B,82B,101C,148C,158C,170C,182C,186C,376C 'ip-bas':81B,185C 'ipspoofattackerror':292C 'issu':548C 'key':193C 'later':502C 'limit':26B,88B,192C,204C,381C 'locat':528C 'log':92B,211C 'luryus':555C 'm73p':12A,17A 'make':470C 'meet':332C 'mitig':439C 'normal':527C 'order':236C 'overwrit':366C,432C,453C 'packag':22B 'pass':68B,138C,405C 'patch':529C 'port':241C,518C 'possibl':1A,183C 'prefer':118C 'present':297C 'prioriti':488C 'proxi':66B,127C,304C,360C,394C,404C,428C,449C,463C 'rack':50B,103C,109C,231C,239C,343C,346C,471C,486C,499C,507C 'rail':23B,31B,95C,243C,338C,482C 'rais':163C,289C 'rate':25B,87B,191C,203C,380C 'rate-limit':24B 'read':35B,111C,224C,350C 'receiv':315C 'releas':254C,519C,522C 'reli':373C 'remain':420C 'remoteip':7A,98C,222C,264C,291C 'remov':364C,430C,451C 'report':305C,551C 'request':104C,514C 'request.forwarded':487C 'request.remote':73B,100C,147C,181C,375C 'requir':336C 'research':554C 'resolut':480C 'respons':550C 'restor':476C 'return':149C 'reus':238C 'revers':126C,448C 'rfc':113C 'rubi':27B 'run':337C 'scheme':515C 'see':320C 'self':29B 'self-host':28B 'send':60B 'serv':357C 'set':72B,396C,414C,464C,485C,495C 'sinc':94C 'spoof':3A,159C,180C,257C,388C 'standard':249C 'string':287C 'suppli':142C,370C,409C,457C 'switch':220C 'therefor':179C 'two':312C 'unauthent':56B,176C 'unchang':146C 'untrust':269C,277C 'upgrad':319C,423C,440C,534C 'use':54B 'version':444C,541C 'whose':303C,403C,462C 'workaround':437C,460C 'x':46B,121C,134C,226C,282C,353C,398C,417C,466C,489C 'x-forwarded-for':45B,120C,133C,225C,281C,352C,397C,416C,465C","nicknames":[],"mitigations":[],"who_affected":"Applications running Rails 8.1.0 through 8.1.4 with Rack 3, served behind a proxy that passes a client-supplied Forwarded header through unchanged, and that use request.remote_ip for access control, rate limiting or auditing. The fix detects a spoofed Forwarded header only when the proxy also sets X-Forwarded-For, so an app whose proxy sets no X-Forwarded-For stays exposed after upgrading unless the proxy strips Forwarded. Apps on Rack 2.2 or on Rails 8.0 are not affected.","tier":"B","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"developers","plain_action":null,"pkeys":["actionpack"],"ptokens":["actionpack"],"labels":["actionpack","cwe348","high","package","rails","ratelimiting","ruby","rubygems","selfhosted"],"campaign_type":null,"search_a":"'2gff':10A,15A '2mmg':11A,16A 'action':5A 'actionpack':18A 'dispatch':6A 'ghsa':9A,14A 'ghsa-2gff-2mmg-m73p':8A,13A 'ip':2A 'm73p':12A,17A 'possibl':1A 'remoteip':7A 'spoof':3A","search_b":"'-348':21B '3':51B '8.1.0':32B '8.1.4':34B 'access':84B 'actionpack':19B 'address':77B 'ahead':43B 'attack':57B 'audit':91B 'base':83B 'bypass':80B 'client':37B 'control':85B 'cwe':20B 'falsifi':90B 'forward':41B,47B,62B 'header':42B,63B 'host':30B 'ip':38B,74B,82B 'ip-bas':81B 'limit':26B,88B 'log':92B 'packag':22B 'pass':68B 'proxi':66B 'rack':50B 'rail':23B,31B 'rate':25B,87B 'rate-limit':24B 'read':35B 'request.remote':73B 'rubi':27B 'self':29B 'self-host':28B 'send':60B 'set':72B 'unauthent':56B 'use':54B 'x':46B 'x-forwarded-for':45B","search_c":"'/luryus).':558C '/rails/rails/commit/2a01a5ac521184419d9a92fa2c25e102e68cd227.patch':540C '/rails/rails/commit/2d084af8444a7b6b868c7cbb256172007871e78d))':219C '2.2':347C '2d084af8':216C '3':110C,344C '3.0.0':500C '7239':114C '8.0':483C '8.1':537C,544C '8.1.0':96C,214C,339C '8.1.4':341C '8.1.4.1':545C 'access':188C,378C 'actiondispatch':97C,221C,263C,290C 'actionpack':322C,446C,543C 'address':131C,151C,174C,197C,209C,271C,279C,300C,308C,479C 'affect':324C,329C,542C 'also':265C,395C,505C 'append':128C 'appli':435C 'applic':325C,327C,402C,461C 'attack':177C 'audit':383C 'avail':497C,524C 'base':187C 'behind':358C 'bypass':184C 'cannot':533C 'caus':506C 'chang':215C 'changelog':323C 'check':160C,258C 'chosen':152C 'client':130C,141C,155C,169C,196C,208C,270C,278C,307C,369C,408C,456C,478C 'client-ip':168C 'client-suppli':140C,368C,407C,455C 'compar':166C,266C 'config.action_dispatch.ip':256C 'configur':426C,447C 'continu':244C 'control':189C,379C 'could':178C 'credit':546C 'default':200C,262C 'deploy':302C 'deriv':99C 'detail':212C 'detect':386C 'determin':512C 'differ':301C 'direct':229C 'enabl':260C 'error':317C 'exact':286C 'expos':421C 'falsifi':206C 'first':268C,276C 'fix':253C,385C,443C,521C 'forward':105C,115C,122C,135C,143C,173C,227C,233C,247C,273C,283C,348C,354C,371C,389C,399C,410C,418C,433C,458C,467C,474C,490C,510C 'github.com':218C,539C 'github.com/rails/rails/commit/2a01a5ac521184419d9a92fa2c25e102e68cd227.patch':538C 'github.com/rails/rails/commit/2d084af8444a7b6b868c7cbb256172007871e78d))':217C 'hackeron':553C 'hackerone.com':557C 'hackerone.com/luryus).':556C 'handl':242C 'header':116C,144C,250C,295C,313C,372C,390C,411C,459C,475C 'honor':246C 'host':516C 'ignor':472C,509C 'impact':93C 'includ':198C 'inconsist':309C 'initi':493C 'ip':101C,148C,158C,170C,182C,186C,376C 'ip-bas':185C 'ipspoofattackerror':292C 'issu':548C 'key':193C 'later':502C 'limit':192C,204C,381C 'locat':528C 'log':211C 'luryus':555C 'make':470C 'meet':332C 'mitig':439C 'normal':527C 'order':236C 'overwrit':366C,432C,453C 'pass':138C,405C 'patch':529C 'port':241C,518C 'possibl':183C 'prefer':118C 'present':297C 'prioriti':488C 'proxi':127C,304C,360C,394C,404C,428C,449C,463C 'rack':103C,109C,231C,239C,343C,346C,471C,486C,499C,507C 'rail':95C,243C,338C,482C 'rais':163C,289C 'rate':191C,203C,380C 'read':111C,224C,350C 'receiv':315C 'releas':254C,519C,522C 'reli':373C 'remain':420C 'remoteip':98C,222C,264C,291C 'remov':364C,430C,451C 'report':305C,551C 'request':104C,514C 'request.forwarded':487C 'request.remote':100C,147C,181C,375C 'requir':336C 'research':554C 'resolut':480C 'respons':550C 'restor':476C 'return':149C 'reus':238C 'revers':126C,448C 'rfc':113C 'run':337C 'scheme':515C 'see':320C 'serv':357C 'set':396C,414C,464C,485C,495C 'sinc':94C 'spoof':159C,180C,257C,388C 'standard':249C 'string':287C 'suppli':142C,370C,409C,457C 'switch':220C 'therefor':179C 'two':312C 'unauthent':176C 'unchang':146C 'untrust':269C,277C 'upgrad':319C,423C,440C,534C 'version':444C,541C 'whose':303C,403C,462C 'workaround':437C,460C 'x':121C,134C,226C,282C,353C,398C,417C,466C,489C 'x-forwarded-for':120C,133C,225C,281C,352C,397C,416C,465C","cause_entry":null,"cause_path":null,"disclosures_checked_at":null,"first_public_at":"2026-11-04T03:01:54+00:00","kev":null,"cvss_vector":null,"cvss_by":null,"poc_count":0,"sources":[{"source":"ghsa-repo","external_id":"GHSA-2gff-2mmg-m73p","url":"https://github.com/rails/rails/security/advisories/GHSA-2gff-2mmg-m73p","severity":"high","published_at":"2026-11-04T03:01:54+00:00","modified_at":"2026-11-04T03:01:54+00:00","first_seen_at":"2026-11-09T22:53:29+00:00","fetched_at":"2026-11-09T22:53:29+00:00","note":null}],"documents":[{"id":110682,"url":"https://github.com/rails/rails/security/advisories/GHSA-2gff-2mmg-m73p","title":"Maintainer's advisory: github.com","kind":"vendor","source":"ghsa-repo","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: github.com"}],"disclosures":[],"exploit_sources":[],"notice":"Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai"}