A reverse proxy can receive two HTTP requests that look equivalent to its cache while the application behind it treats them as different. That disagreement is more than a performance bug. If an attacker can place a response generated from one request variant into a shared cache entry used by other clients, a request that originally affected one connection can acquire a much larger audience.

This is the core security tension in web cache poisoning. The cache key defines which requests are considered interchangeable. The origin application defines which request properties can alter a response. Security depends on those two models staying aligned across proxies, frameworks, routing rules, and application code.

A cache key is an equivalence rule

Shared caches need a compact identity for stored objects. A typical key includes the request scheme, host, path, and query string, though exact behavior varies by product and configuration. Headers may be included selectively. Other request properties may be ignored.

That design is necessary. Including every header would fragment the cache around values that do not affect representation, reducing hit rates and consuming storage. Yet excluding a value is safe only when that value cannot influence a cacheable response in a security-relevant manner.

Consider an application that uses a forwarding header to construct an absolute URL in an HTML page. If the proxy accepts the header from an external client but does not include it in the cache key, two requests can map to the same cache entry even though the application emits different bodies. A malicious host value can become part of the stored page and later be served to clients that never supplied it.

The same pattern can involve path-rewrite headers, alternate host headers, method override fields, localization inputs, or framework-specific routing metadata. The decisive property is not the name of the input. It is a mismatch between data that influences the origin response and data that distinguishes cache entries.

Poisoning requires a usable storage path

Not every unkeyed input creates a practical cache-poisoning condition. The response must also be eligible for shared storage, and the attacker needs a way to cause the altered representation to occupy a key that other clients request.

Cacheability is shaped by status codes, request methods, response directives, authorization handling, cookies, surrogate controls, and product-specific policy. A response carrying Cache-Control: private should not be stored by a conforming shared cache. no-store directs caches not to store the response. Vary can expand representation selection to named request headers, although its operational handling still depends on the cache implementation.

This makes poisoning a system property rather than an isolated application flaw. An origin can emit sensible cache directives while an intermediary applies an override. A content delivery network can have a safe default key while a custom rule removes a query parameter. An application can assume a proxy strips an internal header while a later routing change exposes it to public traffic.

The dangerous state often emerges from individually reasonable choices made in different components.

Normalization can create hidden collisions

Cache identity also depends on normalization. Proxies may canonicalize hosts, decode or preserve selected URL characters, collapse path forms, sort or ignore query parameters, or apply rewrite rules before lookup. Origins and frameworks can perform their own transformations later in the request path.

When those transformations differ, requests that appear distinct at one point can converge at another, or requests treated as equivalent by the cache can diverge at the application.

This is especially relevant around ambiguous URL forms and duplicate parameters. If a cache keys on one interpretation while the application consumes another, the effective representation boundary is no longer visible from either configuration alone. Similar trouble appears when a front proxy rewrites a path but the cache key is derived before or after that rewrite in an unexpected phase.

A robust review therefore follows the request through each transformation: edge parsing, normalization, cache lookup, proxy rewriting, framework parsing, routing, and response generation. The important question is whether every response-affecting distinction survives into the identity used for shared storage.

Host and forwarding metadata deserve strict ownership

Reverse-proxy deployments frequently carry metadata such as the original host, client protocol, or external port. These values are useful when an application must generate redirects or absolute links, but they also cross a trust boundary.

Headers intended to describe proxy-observed state should generally be set by trusted infrastructure rather than accepted as authoritative claims from arbitrary clients. A proxy that merely appends or forwards an attacker-supplied value can transfer control of routing or URL-generation context to the public side of the boundary.

Cache poisoning magnifies that mistake. Without caching, a manipulated absolute link may affect only the attacker’s response. With shared storage, the same output can persist and reach unrelated clients until eviction, expiry, revalidation, or replacement.

This amplification is a useful way to classify risk. The initial input bug determines what an attacker can alter. Cache behavior determines how broadly and how long the altered representation can propagate.

Defensive cache design starts with response semantics

Adding more fields to a cache key is not a universal fix. A large key can mask weak trust boundaries, damage cache efficiency, and still miss an input introduced by future application code. The stronger design starts by deciding which responses are safe to share.

Personalized pages, account-specific data, administrative interfaces, and responses shaped by authorization context require explicit cache policy. Public static assets and genuinely shared representations are easier to reason about because their variation surface is intentionally narrow.

For cacheable dynamic content, the representation dimensions should be deliberate. If language changes the body, the cache strategy needs a controlled language dimension. If device class changes markup, that variation needs a stable representation model rather than arbitrary header reflection. If a request header exists only for trusted proxy-to-origin communication, the edge should remove external copies and write its own value.

Vary is useful when HTTP header-based content negotiation is the intended mechanism, but it is not a general security wrapper. It can create high-cardinality caches, and it does not repair unsafe behavior involving values outside the selected representation model.

Observability must expose cache decisions

Application logs alone rarely show enough context to investigate a poisoning event. The application may record the request that generated a response but have no record of later cache hits, since those requests never reached the origin.

Operational visibility is stronger when edge telemetry records cache status, the effective host and path, relevant normalization outcomes, response age, and a safe representation of key dimensions. Exact cache keys can contain sensitive or high-cardinality data, so observability needs the same care as other request logging.

During an incident, responders need to distinguish generation from distribution. One request may have produced the poisoned object; hundreds of later requests may only have received it. Purging the affected entry can stop distribution, but it does not correct the input trust issue or key mismatch that allowed the object to be stored.

Cache invalidation also has scope. Multi-region content delivery systems may propagate purge operations asynchronously. Layered caches can retain copies at several tiers. Incident plans that assume a single instantaneous deletion can leave residual exposure after the apparent fix.

Security testing has to compare representations

Conventional endpoint testing often asks whether a crafted request produces unsafe output. Cache-aware testing adds another dimension: whether a second, ordinary request can receive output influenced by the first.

That comparison is valuable because it tests the boundary directly. The first request exercises a candidate unkeyed input. The second uses the same expected cache identity without the malicious variation. A response difference that survives into the second request indicates shared state has crossed between request contexts.

Testing needs strict safeguards on production systems. Deliberately inserting altered content into a shared cache can affect real traffic. Controlled environments, isolated cache namespaces, or non-public test keys are safer places to examine storage behavior.

Automated checks can also compare cache configuration with known application variation, but static configuration analysis has limits. Framework middleware, edge functions, feature flags, and runtime routing can all affect the final representation. The most reliable model combines configuration review with observed request and response behavior.

Shared caching expands the blast radius of small mistakes

A cache is often introduced as infrastructure for latency and origin offload, yet its security role is closer to a shared state machine. It decides when one client’s response is suitable for another client. That decision is safe only when request identity and response semantics describe the same boundaries.

The practical goal is not to eliminate caching or to key on every possible input. It is to keep variation intentional: constrain which client-controlled values can influence shared responses, define cacheable representations narrowly, preserve meaningful distinctions in the key, and make intermediary behavior observable.

When those controls drift apart, a minor request-handling inconsistency can become persistent shared content. When they remain aligned, the cache can perform its intended job without silently turning one request’s influence into another client’s response.