Shaduf.
HTTP Cache Field Guide/Cache-key & privacy mismatch

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?

Scope: one response path versus controlled request variants. Sources and model output were retrieved or run on 16 Sep 2026 UTC.

Decision first

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.

From request variant to safe reuse A request is compared by method and target URI, then Vary fields, storage privacy permission, and freshness or validation. A failed gate forwards or revalidates rather than reusing the candidate. Presented requestmethod + URICookie / auth / headers Candidate keyminimum:method + target URIextra material is deployment-specific Match gatesVary fields equal?Fresh or validated?Storage allowed?match ≠ permission ≠ safety Reuse candidatehit → response with marker intactonly after all gates pass miss / mismatch / stale → forward or revalidate
Protocol lens: exact method + target URI is only the minimum selection model. Vary adds nominated request-field constraints; cache policy and freshness are independent gates.

Claim labels used in this report

Normative

Required or defined by an RFC; “MUST” and “MUST NOT” retain their standards meaning.

Browser-facing

Current MDN guidance about developer-visible behavior or recommendations, not a guarantee for every cache.

Vendor/configuration-specific

Selected by a product, framework, service worker, or deployment. The local model is explicitly in this category.

Inference/recommendation

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 modelWhat it tells youWhat it does not tell you
method + target URIExact 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

Accept-Language

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.

Presence matters

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.

Wildcard

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.

Private browser versus shared cache A personalized response can be retained in a private browser cache with private and no-cache, while a shared cache is stopped by private or no-store. Explicit Authorization permission clears only a protocol gate and still requires a safe representation and key. Personalizedresponse body Private browser cacheprivate, no-cache → validatelocal retention; not encryption Shared cache / CDNprivate / no-store → do not shareauth permission is not safety proof Browser outcomefresh → reuse locallystale → conditional request Shared outcomesafe public variant → reuseprivate or unsafe → forward / reject hit
Audience is a separate axis: browser-local retention and shared reuse are different decisions. A fresh hit can still be the wrong person’s bytes if the storage audience or key is wrong.

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-Cookie alone 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=60 alone: freshness information, but no explicit shared-reuse permission.
  • Authorization + public, max-age=60 (or suitable s-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: Authorization adds 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.

IDRequest variantSensitivity of stored responseCache-ControlVarySet-CookieExpected key / match decisionWhat cross-user exposure would look like
Q1Stored GET /item?value=alphaGET /item?value=betaPublic item datapublic, max-age=60Different 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.
Q2Stored GET /account?tenant=AGET /account?tenant=BHigh if tenant selects private datapublic, 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.
L1Stored /home with Accept-Language: en-US → same URI with fr-FRLocalized, normally not user-secretpublic, max-age=60Accept-LanguageNominated values differ; Vary miss, so no reuse without validation.English appears for the French request: a negotiation error, not automatically an account leak.
L2Stored /home with Accept-Language: en-US → same URI with the field absentLocalized/default variantpublic, max-age=60Accept-LanguagePresent versus absent cannot match; Vary miss.A preferred-language or default-language representation reaches the wrong request; wrong language, not necessarily private data.
C0Stored /profile with Cookie absent → same URI with Cookie: sid=AOrigin would personalize for A; stored body is anonymouspublic, 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.
C1Stored /profile with Cookie: sid=A → same URI with Cookie absentHigh, A-personalizedpublic, max-age=60Same method/URI and no Cookie in Vary; match is allowed.Anonymous visitor receives A’s profile, name, order history, or other private body.
C2Stored /profile with Cookie: sid=A → same URI with Cookie: sid=BHigh, personalizedpublic, max-age=60Cookie values are not automatically compared; same method/URI can match.B receives A’s response: the classic shared-cache account leak.
C3Stored /profile with Cookie: sid=A → same URI with Cookie: sid=BHigh, personalizedpublic, max-age=60 (not a safety proof)CookieComplete 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.
S1Stored first response /session → same URI for another client; response sets a session cookieSession-bearing / potentially personalizedpublic, max-age=60sid=<redacted>; Path=/; HttpOnlySet-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.
A0Stored /account with Authorization: token-A → same URI with token-BHigh, authenticatedmax-age=60Shared 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.
A1Stored /account with token-A → same URI with token-BHigh unless intentionally publicpublic, 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 targetUnknown request/context dependencepublic, 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

Node local key/match model. No live shared-cache measurement. 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 sequenceModel status / headers / markerModel implication only
Query alpha → beta → alpha200; 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 LocalTestCacheExact query targets are separate in this model; exact repeat hits.
Accept-Language: en-US → fr-FR → absent → en-US200; 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 Vary200; 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: Cookie200; 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 → repeat200; 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 permission200; both fwd=bypass; stored=?0; markers A then B.The model applies the RFC §3.5 gate.
Auth A → Auth B, public permission200; B is a hit returning marker A.Permission can expose a user-specific representation if key/safety is wrong.
Vary: * → repeat with another header200; second is fwd=vary-miss; stored=?1, not a hit.Vary: * fails matching in this model.
Private Cookie A → B200; 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

  1. Make an authorized controlled origin emit a redacted marker such as X-Variant-Marker: A, B, lang-en, or anon. Never place a real token or personal data in the marker.
  2. 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-Language values, Cookie absent/A/B, and Authorization absent/A/B. Keep redirects, host, port, and POP fixed; record cold/warm state.
  3. 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. A Cache-Status hit is positive evidence only for the named cache that inserted it.
  4. Treat absent Age or Cache-Status as unknown, not “no cache.” RFC 9111 says lack of Age does not imply that the origin was contacted; RFC 9211 makes Cache-Status optional/configurable.
  5. If the response is personalized, fix the storage audience first (private, no-cache or no-store as appropriate), then address missing Vary or URI-key inputs. Do not “fix” a user-specific body with public or by assuming Vary: Cookie is 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 exampleDirectionVerdictPrimary section / contextProvesCannot prove
Cache-Control: public, max-age=60ResponseCorrect syntax; risky on personalized contentRFC 9111 §5.2.2.1, §5.2.2.9, storage §3Freshness lifetime and explicit cacheability, subject to other requirementsSafe body, intended key, or actual storage
Cache-Control: max-age=60 on an Authorization requestResponseFreshness directive; insufficient shared-auth permissionRFC 9111 §3.5, §5.2.2.1How long a permitted entry may be freshThat a shared cache may reuse it
Cache-Control: public, max-age=60 on an authenticated responseResponseExplicit permission; safety remains an application decisionRFC 9111 §3.5, §5.2.2.9Clears the shared-auth gateThat Alice’s representation is safe for Bob
s-maxage=60 or max-age=60, must-revalidateResponseExplicit Authorization alternativesRFC 9111 §3.5, §5.2.2.2, §5.2.2.10Permits shared reuse when requirements are metThat a user-specific body is safe to share
Cache-Control: private, no-cacheResponseStarting policy for browser-local storage plus validationRFC 9111 §5.2.2.4, §5.2.2.7Limits storage to a private cache and requires validation before ordinary reuseEncryption, bfcache/history behavior, or no other layer retained bytes
Cache-Control: no-storeResponseUse when intentional HTTP-cache retention is unacceptableRFC 9111 §5.2.2.5Tells compliant caches not to store/use itErasing an older entry, a compromised cache, or network confidentiality
Vary: Accept-LanguageResponseNegotiation/matching constraintRFC 9110 §12.5.5, RFC 9111 §4.1Requires matching of nominated fieldsStorage permission, privacy safety, or language equivalence
Vary: CookieResponseWhole-field constraint; usually poor privacy defaultRFC 9110 §12.5.5, MDN HTTP cachingSeparates present/different Cookie fields for a compliant matcherPermission, complete personalization coverage, safe key handling, or universal behavior
Vary: *ResponseStored response fails ordinary matchingRFC 9110 §12.5.5, RFC 9111 §4.1Forces forwarding/validation to determine suitabilitySame semantics as no-store or erasure of bytes
Cookie: sid=<redacted>RequestCorrect synthetic Cookie fieldRFC 6265 §4.2, §5.4; MDN CookieClient sent a cookie pairCache keying, response dependence, or privacy
Set-Cookie: sid=<redacted>; Path=/; HttpOnlyResponseCookie-setting example; not cache controlRFC 6265 §4.1, RFC 9111 §7.3; MDN Set-CookieInstructs a user agent; does not inhibit caching by itselfThat a cache will not store/forward it or that the response is safe
Authorization: Bearer <redacted>RequestCorrect credential-bearing requestRFC 9110 §11.6.2; MDN AuthorizationRequest carries credentialsPermission, key correctness, or representation safety
Cache-Status: LocalTestCache; hit; ttl=60ResponseRFC 9211-shaped local diagnosticRFC 9211 §2Named cache says it satisfied request without forwardingOther layers, browser behavior, origin contact elsewhere, or absence of leakage
Via: 1.1 LocalTestCacheResponseIllustrative intermediary markerRFC 9110 §7.6.3Identifies an intermediate protocol/recipient in model outputA 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.

  • 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.
  • Vary compares 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: Cookie compares 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. Absent Age/Cache-Status is not evidence that no cache exists.
  • Cookie processing by user agents, including whether a browser exposes Set-Cookie to script, is distinct from intermediary storage/reuse. This report does not test browser cookie-jar behavior.

Search published pools, pages, reports, and evidence.