Skip to content

Build1 publisher2 min readPublished

Omitting a header from the cache key hands the next visitor someone else's response

Web cache poisoning starts when the origin reads a request field the cache left out of its key, so a response generated for one request gets stored and replayed to everyone asking for that path.

The Engineer · Build desk

Illustration accompanying Omitting a header from the cache key hands the next visitor someone else's response

What happened

  • A cache decides whether two requests are the same by computing a cache key, a representation of the request that determines which stored responses are candidates for reuse.
  • Two requests differing only in an X-Custom-Header value both compute the key /page, so the cache treats them as identical while the origin treats them as different.
  • The attack runs as a miss on the attacker's request, a forward to the origin, storage of the influenced response under /page, then a hit for the next visitor who asks for that path.
  • The dev.to explainer states that the cache is not compromised in this: no unauthorized write occurs, and it stores a legitimate origin response and serves it for a matching key.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint The safe key cannot be chosen at the cache alone, because which fields must be keyed depends on which fields the origin code reads, so the audit spans both systems and both owners.
  • exposure Everyone requesting that path until the entry expires can receive a response shaped by input they never sent, and their browsers have no way to tell the difference.
  • decision Fixing the mismatch by widening the key is also a performance decision: each keyed field narrows what counts as a match, and matching is what delivers the latency and origin-load savings.
  • capability Framing the failure as a key-versus-origin mismatch gives teams something testable, since a diff between the fields the handler reads and the fields the key samples names the candidates.

Two components read the same request and disagree about what it contains. The cache reads its key, and every request that produces that key is the same request as far as the cache is concerned [1]. The origin reads the whole request and can vary its output on any part of it [4]. Nothing in the path enforces agreement between the two [4].

The path makes the mismatch structural: browser, then CDN or reverse proxy or cache, then origin [10]. The program computing equivalence and the program computing the response are different programs, often under different ownership. A change on the origin, such as a handler starting to read a new header, is a change to the cache's correctness assumptions, and the cache configuration does not notice.

The step that does the damage is the miss. The attacker's request carries input the key does not sample, so the cache has nothing stored and forwards to the origin; the origin's response, shaped by that input, comes back and is stored under the plain key; the next request for the same path is a hit [6]. There is no unauthorized write anywhere in that sequence, and no bug to file against the cache, which stored a legitimate origin response and served it for a matching key [7].

So the question is field-level. The dev.to explainer's list of request data beyond the path names four categories: headers, query parameters, cookies and content negotiation fields [3][1]. Whether one of them is in the key decides whether the cache tells apart two requests that differ in it [12]. The check that follows is local. Write down every request field your handler and middleware actually read, then compare that list against the key configuration; anything on the first list and missing from the second can reach a later visitor.

Adding fields is not free. Each keyed field partitions the stored responses, and reuse is what buys the latency and origin-load savings the cache was installed for [11]. Key a per-user cookie and you have a cache holding one response per user.

Whether any of this is a vulnerability in a given stack depends on the reflection. The unkeyed input has to end up in a response header or body in a way that affects clients, and the explainer puts the outcomes on a range running from unexpected behavior to more serious client-side impact [9]. If the response is byte-identical whichever header value arrives, an unkeyed header is a correctness oddity. The post does not name a CDN, a vendor default, or a specific incident, so the severe end of that range stays unquantified.

What to watch

  • Published per-vendor defaults for which headers a CDN keys on would turn this local audit into a checkable list.
  • Measured hit-rate data for keying an additional header or cookie would price the fix instead of leaving it as logic.
  • A disclosed incident tied to one unkeyed field on a named origin would show which reflected inputs matter in production.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories