Skip to content

fix: Parse JSON cache entries safely and stop sharing authenticated responses - #147

Merged
georgeguimaraes merged 3 commits into
masterfrom
security/json-load-and-shared-cache-authorization
Sep 15, 2026
Merged

georgeguimaraes merged 3 commits into
masterfrom
security/json-load-and-shared-cache-authorization

Conversation

@georgeguimaraes

Copy link
Copy Markdown
Member

Fixes for GHSA-p8jg-8p9f-pgmr and GHSA-c33f-42f2-gwcc, reported by Matthew Mongeau (Ruby Central).

  1. BaseStrategy#deserialize_object parses JSON entries with JSON.parse instead of JSON.load, so a json_class key planted through a response header no longer instantiates a class. Cache keys are unchanged.
  2. A shared cache now refuses to store, and refuses to reuse, a response to a request carrying Authorization unless the response has public, must-revalidate or s-maxage (RFC 9111 section 3.5). The read-side check covers entries written by older versions.
  3. README notes for both.

Ships as 2.8.0 since the second change alters shared cache behaviour.

Locally: both reporter PoCs exit 1 before and 0 after; 179 to 186 examples on Ruby 4.0 and 3.3 with Faraday 1 and 2; the new specs fail 5 of 7 against the unpatched lib; rubocop clean.

The default serializer read entries back with JSON.load, which honours a json_class key and calls json_create on that class. Response headers are stored verbatim in the entry, so an origin server could name any loaded class in a single header and have it instantiated on the next cache hit, through both strategies. JSON.parse builds plain Ruby objects only.

Passing create_additions: false to JSON.load is not a fix: the keyword binds to the proc positional parameter and the option never arrives. Cache keys are unchanged, so existing entries stay readable and become inert.
…t permission

A shared cache (the default) stored responses to requests carrying an Authorization header and served them to later requests from other callers whenever the origin omitted Vary and did not mark the response private. RFC 9111 section 3.5 requires a shared cache to leave such responses alone unless they carry public, must-revalidate or s-maxage.

The check runs when storing, so what is never stored is never served, and again when reading, so entries written by earlier versions are treated as a miss rather than reused. Private caches (shared_cache: false) are unchanged.

This changes behaviour for applications that relied on shared cache hits for authenticated responses. Origins can opt back in with Cache-Control: public or s-maxage.

Release-As: 2.8.0
…the README

Only recommend Marshal for fully trusted stores, since Marshal.load instantiates whatever is in the data, and document the RFC 9111 section 3.5 rule for responses to authenticated requests.
@georgeguimaraes
georgeguimaraes merged commit 64754d5 into master Sep 15, 2026
10 checks passed
@georgeguimaraes
georgeguimaraes deleted the security/json-load-and-shared-cache-authorization branch September 15, 2026 20:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant