Cache Keys Are Security Boundaries at the HTTP Edge

A reverse proxy can receive two requests that an application considers different and still treat them as the same cache entry. That gap is enough to turn a response intended for one request context into a response served to many others.

The issue is not caching in isolation. It is disagreement about identity. Applications make decisions from headers, query parameters, cookies, paths, host information, and sometimes values added by upstream infrastructure. A shared cache uses a smaller set of inputs to decide whether a stored response matches a later request. If an input changes application behavior but does not participate in the cache key, that input crosses a security boundary without being represented in cache identity.

This is the foundation of web cache poisoning and several adjacent failure modes. A crafted request influences a cacheable response, the cache stores it under a key that omits the influential input, and ordinary requests later receive the stored result.

The cache key defines equivalence

A cache key is more than an optimization detail. It defines which requests the caching layer considers equivalent.

At a simple edge, the key may include scheme, host, path, and query string. Real deployments add complications: query normalization, selected request headers, device variants, language negotiation, compression, cookies, and vendor-specific rules. Some inputs are intentionally excluded to keep the cache efficient.

That exclusion becomes dangerous when the origin uses one of those inputs to construct the response.

Consider an application that builds an absolute URL from a forwarding header supplied by its reverse proxy. If the edge accepts that header from the public request, forwards it unchanged, and does not include it in the cache key, a hostile value can affect generated markup without changing cache identity. A cacheable response containing the hostile host could then be stored for the normal path.

The exact header is not the central point. The same structural defect appears whenever an unkeyed request property controls response content, redirects, script references, security metadata, or another observable decision.

Not every unkeyed input creates a vulnerability. Many headers legitimately do not affect representation. The security question is whether an attacker-controlled value can alter a response that is subsequently reusable for requests lacking that value.

Normalization can create a second interpretation gap

Caches often normalize request components before lookup. Origins and application frameworks also parse and normalize requests, but not necessarily in the same manner.

A cache might sort query parameters, ignore selected parameters, decode some characters, collapse path forms, or apply case rules. An origin may preserve distinctions that the cache removes. If two syntactically different requests map to one cache key while producing different origin behavior, an attacker can gain influence over the representation stored for that key.

The reverse mismatch also matters. A cache can preserve a distinction that an intermediary or origin collapses, creating unexpected routing or policy behavior across layers.

This makes cache security resemble other multi-parser problems in HTTP infrastructure. The risk is produced by composition. Each component can behave consistently according to its own configuration while the chain as a whole assigns different meanings to the same request.

Canonicalization therefore needs an architectural owner. It is not enough to know that the CDN normalizes a path or that the framework parses a query string. The relevant property is that routing, authorization, representation selection, and cache lookup agree on the distinctions that carry security meaning.

Personalized responses need a hard boundary

The most damaging cache failures are often simpler than exotic parser discrepancies: a response containing user-specific data becomes eligible for shared storage.

Authentication does not automatically prevent caching. HTTP caching behavior depends on response directives, request conditions, cache configuration, and implementation policy. A reverse proxy or CDN can also be configured to override or ignore origin directives.

Applications that vary responses by session state need an explicit cache strategy. One common design is to keep authenticated or personalized routes out of shared caches. Another is to cache only a public shell while retrieving private data through separately authenticated requests. Systems that deliberately cache multiple variants need keys that represent every security-relevant dimension and controls that prevent private variants from becoming public ones.

Cookies deserve particular attention. Including an entire Cookie field in a key can destroy cache efficiency, so platforms often bypass caching when session cookies are present or key only on selected state. That is an operational trade-off, not a reason to let application behavior and cache policy drift apart.

The Vary response field can tell conforming caches that selected request header fields affect representation selection. It is useful for content negotiation and related cases, but it is not a universal cache-security mechanism. It does not express arbitrary application state, and deployment-specific cache rules can still determine whether a response is stored or reused.

Poisoning requires persistence, not just reflection

A reflected request value can look alarming during testing without creating cache poisoning. For poisoning to affect other requests, the response must be cacheable, the attacker must be able to influence it, and later requests must map to the same stored object.

Those conditions make cache behavior observable in ways application testing often misses. A direct request to the origin can be safe while the same route becomes vulnerable behind an edge cache. Conversely, a suspicious reflection may never persist because the response is uncacheable or the influential input is already part of the key.

Cache status metadata, age information, request IDs, and controlled comparisons between repeated requests can help operators establish what the edge actually stored and reused. The important evidence is not simply that a header changed a response. It is that the changed response crossed request contexts through shared storage.

Eviction and expiration affect exploit reliability rather than the core defect. A short lifetime can reduce exposure, but a poisonable object with a ten-second lifetime is still a security problem if traffic repeatedly refreshes it or if the affected endpoint receives sensitive traffic during that interval.

Edge-generated responses expand the surface

The origin application is not the only component capable of producing cacheable content. CDNs, API gateways, reverse proxies, image services, localization layers, and edge compute functions can generate or transform responses before storage.

That broadens the set of inputs that must align with cache identity. An edge function might select a tenant from a hostname, rewrite a path, add a country-specific variant, or generate a redirect. If the resulting object is cached under a key that omits the decision input, the inconsistency exists entirely at the edge.

Error handling is another subtle area. Custom 404 pages, redirects, authorization failures, and upstream error responses may be cached under some configurations. If attacker-controlled request data appears in those responses, caching policy needs to account for it just as carefully as successful content.

The safest review boundary is therefore the complete request-to-cache pipeline, not only application code. Any component that changes representation before a shared storage decision participates in the cache security model.

Purging is incident response, not remediation

Once a poisoned object is discovered, removing it from shared caches can stop immediate delivery. Purging does not correct the condition that allowed the object to be stored under an unsafe identity.

A durable fix changes one of the structural properties: the untrusted input no longer affects the response, the input becomes part of the correct cache variation, the response is no longer shared, or the edge rejects the input before it reaches a component that trusts it.

That distinction matters during incident handling. A purge can create a brief appearance of recovery while the same request can repopulate the cache seconds later.

Configuration changes also need to propagate across the full serving fleet. Multi-region CDNs and layered caches can retain objects at several tiers. Validation after remediation should confirm both request interpretation and cache behavior at the paths users actually traverse.

Cache design belongs in the threat model

Shared caches amplify. That is their performance value and their security consequence. A single origin response can be delivered to a large population without the origin evaluating each later request.

The cache key is the control that decides which requests are allowed to share that response. Treating it as a security boundary changes design reviews in a useful way: every request input that can affect a cacheable representation must have an explicit relationship to cache identity.

When that relationship is documented and enforced, caching remains a controlled reuse mechanism. When it is implicit, small differences between proxy configuration and application behavior can acquire the reach of the entire edge.