Possible cache poisoning in Action Pack content negotiation
Published: November 04, 2026
SECURITY IDENTIFIERS
- GHSA: GHSA-r9jg-fmfx-2gv6
- Vendor Advisory: https://github.com/rails/rails/security/advisories/GHSA-r9jg-fmfx-2gv6
GEM
FRAMEWORK
SEVERITY
CVSS v3.x: 4.8 (Medium)
PATCHED VERSIONS
~> 8.0.5, >= 8.0.5.2
>= 8.1.4.1
DESCRIPTION
Impact
Action Pack negotiates the format of a rendered response from the Accept,
X-Requested-With and, for XHR requests without Accept, Content-Type
request headers, but ActionController::Rendering#_set_vary_header adds
Vary: Accept only when the request carries a usable Accept header and the
response has no Vary header yet. A response to a request without Accept,
or to an XHR request, therefore omits the headers that selected its
representation, and a response whose Vary the application set before
rendering (for example Vary: Accept-Language) never gains Accept. A shared
cache that honours Vary could store such a response and serve it to later
clients that request a different representation, possibly leading to cache
poisoning in which visitors receive, until the entry expires, a page without
its layout or HTML in place of JSON. The attacker cannot choose the content of
the response.
Applications that render negotiated responses from actions marked public
(for example with expires_in ..., public: true) behind a shared cache that
honours Vary, such as Thruster or Rack::Cache, are affected.
Details
In the fixed releases, a rendered response whose format is not given by the
URL lists Accept and X-Requested-With in Vary, and also Content-Type
for an XHR request without Accept; when
config.action_dispatch.ignore_accept_header is enabled, it lists only
X-Requested-With. These fields are appended to an existing Vary value
rather than skipped, and Vary: * is left unchanged. Shared caches can store
more variants of such responses than before. See the Action Pack CHANGELOG for
details.
Workarounds
Set Vary: Accept, X-Requested-With on negotiated responses in the
application, for example in an after_action, or do not mark negotiated
responses public.
Releases
The fixed releases are available at the normal locations.
Patches for those who cannot upgrade at once:
- 8.1: https://github.com/rails/rails/commit/7a955ca36779938267c93902c47d1f55bab99b4b.patch
- 8.0: https://github.com/rails/rails/commit/b58c107abe9c1f10c1d6a4941402c60e4dc4742c.patch
Credit
This issue was found by the Anthropic OSS Scanner (ANT-2026-7GHGLQGO, ANT-2026-5MT6C2R3).
RELATED
- https://rubygems.org/gems/actionpack/versions/8.1.4.1
- https://github.com/rails/rails/releases/tag/v8.1.4.1
- https://rubygems.org/gems/actionpack/versions/8.0.5.2
- https://github.com/rails/rails/releases/tag/v8.0.5.2
- https://github.com/rails/rails/commit/7a955ca36779938267c93902c47d1f55bab99b4b
- https://github.com/rails/rails/commit/b58c107abe9c1f10c1d6a4941402c60e4dc4742c
- https://github.com/rails/rails/security/advisories/GHSA-r9jg-fmfx-2gv6
