---
id: "GHSA-r9jg-fmfx-2gv6"
page: "https://infrastack.ai/live/GHSA-r9jg-fmfx-2gv6"
kind: "vulnerability"
aliases: ["GHSA-r9jg-fmfx-2gv6"]
title: "Possible cache poisoning in Action Pack content negotiation"
severity: "medium"
cvss: 4.8
epss: null
exploited: false
status: "open"
category: "package"
products: [{"name": "actionpack", "fixed": ["8.1.4.1"], "affected": ">= 8.1, < 8.1.4.1", "ecosystem": "rubygems"}, {"name": "actionpack", "fixed": ["8.0.5.2"], "affected": "< 8.0.5.2", "ecosystem": "rubygems"}]
ecosystems: ["rubygems"]
tags: ["actionpack", "cwe-436", "package", "rails", "ruby", "self-hosted"]
not_affected_if: ["no-public-caching", "no-shared-cache", "single-format-responses"]
published_at: "2026-11-04T03:02:08+00:00"
first_seen_at: "2026-11-09T22:53:29+00:00"
updated_at: "2026-11-09T22:57:03+00:00"
sources: [{"source": "ghsa-repo", "url": "https://github.com/rails/rails/security/advisories/GHSA-r9jg-fmfx-2gv6", "first_seen_at": "2026-11-09T22:53:29+00:00"}]
exploit_sources: []
---

# Possible cache poisoning in Action Pack content negotiation

Link for people: https://infrastack.ai/live/GHSA-r9jg-fmfx-2gv6 (this .md is for agents)

Action Pack omits the Vary header that records which request headers chose a response's format, so a shared cache can store a response and serve it to clients asking for a different format. An attacker cannot choose the response content, but visitors may receive a page without its layout or HTML in place of JSON until the cache entry expires.

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- rubygems:actionpack affected >= 8.1, < 8.1.4.1 fixed 8.1.4.1
- rubygems:actionpack affected < 8.0.5.2 fixed 8.0.5.2

## How to check

- Run `bundle exec rails -v` or check `Gemfile.lock` for `actionpack` below `8.1.4.1` or below `8.0.5.2`.
- Look for actions marked `public` (for example `expires_in ..., public: true`) that render negotiated responses behind a cache such as Thruster or `Rack::Cache`.
- Check whether `config.action_dispatch.ignore_accept_header` is enabled.

## Fix

- actionpack 8.1 series: move to `8.1.4.1`
- actionpack 8.0 series: move to `8.0.5.2`

## Timeline

- 2026-11-09T22:53:29+00:00 ghsa-repo https://github.com/rails/rails/security/advisories/GHSA-r9jg-fmfx-2gv6

## Documents

- [advisory] Maintainer's advisory: github.com https://github.com/rails/rails/security/advisories/GHSA-r9jg-fmfx-2gv6

## From the sources

### 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).

---

_Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai_
