Focused follow-up · 16 Sep 2026
Cache-key & privacy mismatch
When can one cached HTTP response be reused for another request—and how can you catch the wrong-person’s bytes before release?
A candidate response must pass key, Vary, freshness, and storage/privacy gates. A match only says “this entry may be selected”; it does not grant permission to store or reuse a response, and it does not prove that the body is safe to share.
Cookie and Authorization are separate: a request Cookie is not automatically in HTTP’s minimum cache key, while a shared cache needs an explicit public, s-maxage, or must-revalidate permission for a request carrying Authorization. Neither rule makes a personalized representation safe to share.
Vary adds nominated request-field constraints; cache policy and freshness are independent gates.Claim labels used in this report
Required or defined by an RFC; “MUST” and “MUST NOT” retain their standards meaning.
Current MDN guidance about developer-visible behavior or recommendations, not a guarantee for every cache.
Selected by a product, framework, service worker, or deployment. The local model is explicitly in this category.
A privacy or testing recommendation derived from the normative rules and the stated threat model.
1 · URI first: query values normally select different targets
Normative RFC 9111 §2 defines the minimum cache key as the request method plus target URI. RFC 9110 §7.1 says the target URI excludes only a fragment; path and optional query identify the target resource. Under that minimum model, /item?value=alpha and /item?value=beta are distinct targets and do not select one another’s stored response.
That is not a promise about application meaning or every deployed key. A cache, routing layer, framework, or CDN may normalize, remove, sort, decode, or otherwise treat query parameters specially. HTTP leaves those extra choices to implementation and configuration. If tenant=A changes the representation but an intermediary strips tenant, the whole path has a cache-key/privacy defect even though exact target-URI matching would have separated the original URIs.
| Minimum model | What it tells you | What it does not tell you |
|---|---|---|
method + target URI | Exact query targets are different candidates; caches may add material such as Vary fields. | Whether a cache stores, normalizes a query, honors an application key, or is present at all. |
2 · Vary narrows matching; it does not authorize sharing
Different values
Vary: Accept-Language makes en-US and fr-FR different nominated values, unless field semantics establish equivalence. This is a negotiation constraint, not a user-privacy guarantee.
Absent versus present
A missing nominated field matches only another missing field. A request without Accept-Language does not silently match one with Accept-Language: en-US.
Vary: *
The stored response always fails ordinary Vary matching without revalidation: other request/context aspects may have influenced selection. It is not the same directive as no-store; retained bytes are not automatically erased.
RFC 9110 §12.5.5 identifies the request fields that might have influenced selection; RFC 9111 §4.1 requires the nominated values from the original and presented requests to match before a stored response is used without revalidation. Vary does not grant storage permission: no-store, private, or the authenticated-request rule can independently stop storage/reuse. Current MDN likewise recommends private for personalized content instead of trying to protect it with a cookie in Vary, and recommends consistent Vary values on 200/default/304 responses.
3 · Cookie and Authorization are separate privacy cases
Cookie: field, store, and response policy
Normative RFC 6265 defines Cookie as the user agent’s serialization of applicable stored cookies; the user agent may omit the field. That cookie-state rule does not define an HTTP cache key.
- No automatic keying:
Cookie: sid=…alone does not require partitioning by its value. Vary: Cookie: a compliant matcher compares the complete field, so differing values and present/absent fields miss.- Not a safety proof: it does not account for authorization, query normalization, server state, device/geo/IP, or other unlisted inputs.
Set-Cookiealone is not a barrier: RFC 9111 §7.3 says a cacheable response with it can satisfy later requests.
Recommendation For cookie-personalized content, start with private, no-cache; use no-store when intentional retention is unacceptable. Use Vary: Cookie only for a deliberately shareable variant scheme.
Authorization: permission before shared reuse
Normative RFC 9111 §3.5 says a shared cache must not reuse a response to a request carrying Authorization unless the response explicitly permits it with public, s-maxage, or must-revalidate.
Authorization+max-age=60alone: freshness information, but no explicit shared-reuse permission.Authorization+public, max-age=60(or suitables-maxage/must-revalidate): the protocol gate is cleared, subject to all other rules.- Permission is not safety: a user-specific “Alice” body can still collide with Bob when the key is only method + URI.
Vary: Authorizationadds a match constraint; it does not remove the permission requirement or replace the privacy decision.
Recommendation Share an authenticated response only when the representation is intentionally identical and the key is deliberately safe—never merely because it is authenticated.
4 · Adversarial cache-key / privacy worksheet
Read every row as stored response → presented request. “Match” means a candidate may be selected; it does not mean the entry is fresh, permitted to be reused, or safe to share. — means absent.
| ID | Request variant | Sensitivity of stored response | Cache-Control | Vary | Set-Cookie | Expected key / match decision | What cross-user exposure would look like |
|---|---|---|---|---|---|---|---|
| Q1 | Stored GET /item?value=alpha → GET /item?value=beta | Public item data | public, max-age=60 | — | — | Different target URIs; no match under the minimum method+URI model. | If a layer drops/rewrites the query, item A is returned for B; a tenant selector can become tenant-data leakage. |
| Q2 | Stored GET /account?tenant=A → GET /account?tenant=B | High if tenant selects private data | public, max-age=60 (unsafe if private) | — | — | Exact target URIs differ in the protocol model; verify query normalization. | Tenant A’s body appears for B when a layer ignores/normalizes tenant or the app also uses an unkeyed cookie. |
| L1 | Stored /home with Accept-Language: en-US → same URI with fr-FR | Localized, normally not user-secret | public, max-age=60 | Accept-Language | — | Nominated values differ; Vary miss, so no reuse without validation. | English appears for the French request: a negotiation error, not automatically an account leak. |
| L2 | Stored /home with Accept-Language: en-US → same URI with the field absent | Localized/default variant | public, max-age=60 | Accept-Language | — | Present versus absent cannot match; Vary miss. | A preferred-language or default-language representation reaches the wrong request; wrong language, not necessarily private data. |
| C0 | Stored /profile with Cookie absent → same URI with Cookie: sid=A | Origin would personalize for A; stored body is anonymous | public, max-age=60 (unsafe if origin varies on Cookie) | — | — | Cookie is not automatically key material; match is allowed in the minimum model. | A receives anonymous content instead of A’s content: a variant mismatch, not the leak direction. |
| C1 | Stored /profile with Cookie: sid=A → same URI with Cookie absent | High, A-personalized | public, max-age=60 | — | — | Same method/URI and no Cookie in Vary; match is allowed. | Anonymous visitor receives A’s profile, name, order history, or other private body. |
| C2 | Stored /profile with Cookie: sid=A → same URI with Cookie: sid=B | High, personalized | public, max-age=60 | — | — | Cookie values are not automatically compared; same method/URI can match. | B receives A’s response: the classic shared-cache account leak. |
| C3 | Stored /profile with Cookie: sid=A → same URI with Cookie: sid=B | High, personalized | public, max-age=60 (not a safety proof) | Cookie | — | Complete Cookie fields differ; Vary miss, so no reuse without validation. | If a cache ignores Vary, strips Cookie, or another input changes the body, A can still reach B; private/no-store is the safer default. |
| S1 | Stored first response /session → same URI for another client; response sets a session cookie | Session-bearing / potentially personalized | public, max-age=60 | — | sid=<redacted>; Path=/; HttpOnly | Set-Cookie alone does not inhibit storage; same key can match and reuse. | B can receive A’s session cookie or personalized body. Repeated Set-Cookie is a red flag, not proof of one specific browser behavior. |
| A0 | Stored /account with Authorization: token-A → same URI with token-B | High, authenticated | max-age=60 | — | — | Shared reuse is prohibited: no public, s-maxage, or must-revalidate. | If a hit is observed, B sees A’s account body; inspect the actual shared hop and its policy. |
| A1 | Stored /account with token-A → same URI with token-B | High unless intentionally public | public, max-age=60 (or s-maxage=60 / must-revalidate) | — | — | Explicit permission clears the shared-auth gate; the minimum key can still collide. | B receives A’s representation. The directive made reuse permitted, not safe; do not use it for a user-specific body. |
| V* | Stored response with Vary: * → any later request for the same target | Unknown request/context dependence | public, max-age=60 | * | — | Always fails ordinary Vary matching without revalidation; not no-store. | A direct hit indicates a cache/configuration problem; context-selected bytes could expose the selecting context. |
5 · Expected protocol outcomes versus local model output
curl was unavailable and loopback listening was denied with EPERM. No external endpoint, credentials, browser, CDN, service worker, or user data was used. The deterministic test therefore modeled a synthetic origin plus an instrumented local cache. Its key was method + exact target URI; its matcher enforced present/absent Vary values and Vary: *; its shared-store gate handled no-store, private, and Authorization without explicit permission.Expected protocol outcomes The worksheet above is the normative decision aid: exact query targets differ; nominated Vary fields must match; Cookie is not automatically a key; Set-Cookie alone is not a cache barrier; and Authorization needs explicit shared-reuse permission. These expectations do not force a browser, proxy, CDN, framework, or service worker to behave identically.
| Local model sequence | Model status / headers / marker | Model implication only |
|---|---|---|
Query alpha → beta → alpha | 200; first two fwd=uri-miss; stored=?1, final hit; ttl=60; markers query-alpha, query-beta, query-alpha; Age null on forward and 0 on hit; Via: 1.1 LocalTestCache | Exact query targets are separate in this model; exact repeat hits. |
Accept-Language: en-US → fr-FR → absent → en-US | 200; French and absent are fwd=vary-miss; stored=?1; repeat English is a hit; Vary: Accept-Language retained. | Values and missing/present are separate variants in this model. |
| Cookie A → B → absent, no Vary | 200; B and absent are hit; ttl=60, both returning marker cookie-A; Vary null. | Demonstrates the dangerous minimum model: Cookie changes do not automatically change the key. |
Cookie A → B → absent → B, Vary: Cookie | 200; B and absent are fwd=vary-miss; stored=?1; final B is a hit with marker cookie-B. | Demonstrates matching protection only in this model. |
Set-Cookie response → repeat | 200; repeat is a hit with Age: 0, model Cache-Status: … hit, Via, and the redacted Set-Cookie still present. | Set-Cookie alone did not block model storage/reuse. |
| Auth A → Auth B, no permission | 200; both fwd=bypass; stored=?0; markers A then B. | The model applies the RFC §3.5 gate. |
Auth A → Auth B, public permission | 200; B is a hit returning marker A. | Permission can expose a user-specific representation if key/safety is wrong. |
Vary: * → repeat with another header | 200; second is fwd=vary-miss; stored=?1, not a hit. | Vary: * fails matching in this model. |
| Private Cookie A → B | 200; both fwd=bypass; stored=?0; markers A then B. | The model keeps the private response out of a shared store. |
Interpretation limit: a model hit proves only that this model did not call its synthetic origin function. It cannot prove storage, reuse, validation, forwarding, origin contact, or absence of leakage anywhere else.
6 · A safe pre-deploy adversarial check
- Make an authorized controlled origin emit a redacted marker such as
X-Variant-Marker: A,B,lang-en, oranon. Never place a real token or personal data in the marker. - Warm the intended shared hop with variant A. Then use a fresh client for the same target with variant B. Repeat with two query values, two
Accept-Languagevalues, Cookie absent/A/B, and Authorization absent/A/B. Keep redirects, host, port, and POP fixed; record cold/warm state. - At the same hop, capture status and
Cache-Control,Vary,Set-Cookie,Age,Cache-Status,Via, and the redacted marker. Marker A on B is a fail even if headers look cacheable. ACache-Statushit is positive evidence only for the named cache that inserted it. - Treat absent
AgeorCache-Statusas unknown, not “no cache.” RFC 9111 says lack ofAgedoes not imply that the origin was contacted; RFC 9211 makesCache-Statusoptional/configurable. - If the response is personalized, fix the storage audience first (
private, no-cacheorno-storeas appropriate), then address missingVaryor URI-key inputs. Do not “fix” a user-specific body withpublicor by assumingVary: Cookieis a confidentiality control.
Unexecuted live-capture template
The following commands were not run here. Use only an authorized endpoint with synthetic identities and capture at the intended shared hop.
URL='https://authorized.example.test/profile' # Warm with a synthetic test identity A; do not use a production token. curl -sS -D /tmp/a.headers -o /tmp/a.body \ -H 'Cookie: sid=<synthetic-A>' "$URL" # Present a different synthetic identity B to the same target. curl -sS -D /tmp/b.headers -o /tmp/b.body \ -H 'Cookie: sid=<synthetic-B>' "$URL" # Inspect the same-hop evidence without printing credential values. grep -iE '^(HTTP/|cache-control:|vary:|set-cookie:|age:|cache-status:|via:|x-variant-marker:)' \ /tmp/a.headers /tmp/b.headers
Do not interpret one 200, one 304, a missing Age, or a missing Cache-Status as proof of storage, reuse, origin contact, or no leakage. Use a warm/cold pair and a redacted marker at the intended shared hop.
Header-check record · retrieved/cross-checked 16 Sep 2026 UTC
| Exact example | Direction | Verdict | Primary section / context | Proves | Cannot prove |
|---|---|---|---|---|---|
Cache-Control: public, max-age=60 | Response | Correct syntax; risky on personalized content | RFC 9111 §5.2.2.1, §5.2.2.9, storage §3 | Freshness lifetime and explicit cacheability, subject to other requirements | Safe body, intended key, or actual storage |
Cache-Control: max-age=60 on an Authorization request | Response | Freshness directive; insufficient shared-auth permission | RFC 9111 §3.5, §5.2.2.1 | How long a permitted entry may be fresh | That a shared cache may reuse it |
Cache-Control: public, max-age=60 on an authenticated response | Response | Explicit permission; safety remains an application decision | RFC 9111 §3.5, §5.2.2.9 | Clears the shared-auth gate | That Alice’s representation is safe for Bob |
s-maxage=60 or max-age=60, must-revalidate | Response | Explicit Authorization alternatives | RFC 9111 §3.5, §5.2.2.2, §5.2.2.10 | Permits shared reuse when requirements are met | That a user-specific body is safe to share |
Cache-Control: private, no-cache | Response | Starting policy for browser-local storage plus validation | RFC 9111 §5.2.2.4, §5.2.2.7 | Limits storage to a private cache and requires validation before ordinary reuse | Encryption, bfcache/history behavior, or no other layer retained bytes |
Cache-Control: no-store | Response | Use when intentional HTTP-cache retention is unacceptable | RFC 9111 §5.2.2.5 | Tells compliant caches not to store/use it | Erasing an older entry, a compromised cache, or network confidentiality |
Vary: Accept-Language | Response | Negotiation/matching constraint | RFC 9110 §12.5.5, RFC 9111 §4.1 | Requires matching of nominated fields | Storage permission, privacy safety, or language equivalence |
Vary: Cookie | Response | Whole-field constraint; usually poor privacy default | RFC 9110 §12.5.5, MDN HTTP caching | Separates present/different Cookie fields for a compliant matcher | Permission, complete personalization coverage, safe key handling, or universal behavior |
Vary: * | Response | Stored response fails ordinary matching | RFC 9110 §12.5.5, RFC 9111 §4.1 | Forces forwarding/validation to determine suitability | Same semantics as no-store or erasure of bytes |
Cookie: sid=<redacted> | Request | Correct synthetic Cookie field | RFC 6265 §4.2, §5.4; MDN Cookie | Client sent a cookie pair | Cache keying, response dependence, or privacy |
Set-Cookie: sid=<redacted>; Path=/; HttpOnly | Response | Cookie-setting example; not cache control | RFC 6265 §4.1, RFC 9111 §7.3; MDN Set-Cookie | Instructs a user agent; does not inhibit caching by itself | That a cache will not store/forward it or that the response is safe |
Authorization: Bearer <redacted> | Request | Correct credential-bearing request | RFC 9110 §11.6.2; MDN Authorization | Request carries credentials | Permission, key correctness, or representation safety |
Cache-Status: LocalTestCache; hit; ttl=60 | Response | RFC 9211-shaped local diagnostic | RFC 9211 §2 | Named cache says it satisfied request without forwarding | Other layers, browser behavior, origin contact elsewhere, or absence of leakage |
Via: 1.1 LocalTestCache | Response | Illustrative intermediary marker | RFC 9110 §7.6.3 | Identifies an intermediate protocol/recipient in model output | A complete path when layers omit/rewrite it |
Sources and evidence limits
RFC links are primary normative sources; MDN links are current developer-facing cross-checks. All source pages below were retrieved or cross-checked on 16 Sep 2026 UTC. No vendor documentation was needed.
- RFC 9111 §2 · minimum cache key
- RFC 9111 §3 · storing responses
- RFC 9111 §3.5 · authenticated requests
- RFC 9111 §4 · constructing responses
- RFC 9111 §4.1 · Vary matching
- RFC 9111 §5.2 · Cache-Control
- RFC 9111 §7.3 · sensitive information / Set-Cookie
- RFC 9110 §7.1 · target resource
- RFC 9110 §7.6.3 · Via
- RFC 9110 §11.6.2 · Authorization
- RFC 9110 §12.5.5 · Vary
- RFC 6265 · HTTP State Management
- RFC 9211 §2 · Cache-Status
- MDN · HTTP caching
- MDN · Cache-Control
- MDN · Vary
- MDN · Cookie
- MDN · Set-Cookie
- MDN · Authorization
- The RFC minimum key is not a prescription for a browser, service-worker Cache API, reverse proxy, CDN, framework, or vendor’s extra key. Query normalization, host/authority handling, partitioning, cookie handling, request coalescing, and POP selection remain deployment-specific.
- This run did not measure a real shared cache. The local model cannot establish that a production layer stores, reuses, validates, forwards, or strips the same response.
Varycompares nominated request fields; it cannot describe arbitrary server state, network address, TLS/session context, or application logic not represented in the message.Vary: *is a coarse “cannot know without origin” signal.Vary: Cookiecompares a complete field value, not a parsed identity. Ordering, duplicate names, irrelevant cookies, partitioning, and application parsing can create cardinality or correctness problems; this report does not prescribe universal canonicalization.- Explicit Authorization permission is a protocol gate, not an authorization model or confidentiality guarantee. Whether a representation is safe to share remains an application/deployment judgment.
- A 200/304,
Age,Cache-Status,Via, browser DevTools row, or origin counter identifies only the layer that emitted or recorded it. AbsentAge/Cache-Statusis not evidence that no cache exists. - Cookie processing by user agents, including whether a browser exposes
Set-Cookieto script, is distinct from intermediary storage/reuse. This report does not test browser cookie-jar behavior.