Website problems
User-specific response cached publicly
Cache-Control decides which caches may store a response. A response carrying data about one person, sent with a directive that permits shared caching, can be stored by a CDN or a proxy and handed to the next caller of the same URL. Nothing is compromised and nothing is breached — the infrastructure does exactly what it was told. This is one of the few configuration mistakes whose consequence is one account seeing another's data.
The distinction the whole problem rests on
A private cache is the browser's own store; what it keeps is only ever returned to the person it belongs to.
A shared cache is anything in between — a CDN edge, a reverse proxy, a corporate proxy — and what it stores is served to whoever asks for the same URL next.
`private` permits the browser only. `public` and a bare `max-age` permit both. `no-store` permits neither.
So the question is never whether a response is cacheable but which cache is being given permission.
For a user-specific payload the answer has to exclude the shared one, and an absent directive does not exclude it — it leaves the decision to heuristics.
Why it does not show up before production
In development there is usually no shared cache in the path, so a wrong directive has no observable consequence.
Tests pass, review passes, and the behaviour is identical with or without the mistake.
The consequence appears the day a CDN or a proxy is put in front of the service, which is typically true in production and typically not true anywhere else.
Which is also why the symptom is reported as intermittent: it depends on whether a cache entry existed, which depends on traffic.
And why a support report of someone seeing the wrong data is so often diagnosed as an application bug that cannot be reproduced.
The Authorization header does not save you
Some caches treat an authenticated request as uncacheable by default, and not all of them do, and the rules differ by cache.
Relying on that behaviour means relying on every intermediary between you and every visitor implementing the same default.
The reliable answer is an explicit directive: `no-store` for anything user-specific, or `private` with a short lifetime where a browser cache genuinely helps.
Where an authenticated response must be cacheable at all, `Vary: Authorization` is what stops a cache keying two different callers to one entry.
Explicit beats inherited here, because the inheritance is not consistent enough to depend on.
What to send for each kind of response
Account data, orders, sessions, anything per-person: `no-store`.
Something expensive and per-person that the same browser will re-request: `private` with a short lifetime.
Public reference data that is the same for everyone: a real `max-age`, because those are exactly the responses a shared cache should store.
Anything whose content depends on a request header: the corresponding `Vary`, or a cache will serve one variant to another client.
An explicit directive on every API response, since the absence of one is not neutrality.
How to check it yourself
Sign in, open the network panel, and read the Cache-Control header on the responses that carry your own data.
Anything with `public` or a bare `max-age` on a per-person payload is the finding.
Check from outside the application too: `curl -I` against the same URL shows what the edge sends rather than what the origin intended.
Look for the CDN's cache status header, which says whether the edge stored the response.
Re-check after any gateway or CDN configuration change, since header rewriting at that layer replaces whatever the application sent.
How VeriFixScan detects it
`api.cache.policy` reads Cache-Control on the observed API responses and flags user-specific paths that a shared cache is permitted to store.
`api.surface.detected` supplies the endpoints examined, so the check stays inside the discovered inventory.
`performance.cdn_cache_status` reports the edge's own hit and miss headers, which is what shows a shared cache is really in the path.
`api.cors.vary` and `performance.cache_control` cover the neighbouring variation and document-level caching questions.
Whether a path is user-specific is judged from its shape and payload rather than from your data model, so the classification is a strong indication rather than a certainty.
Why this is worth treating as urgent
Most configuration findings cost performance, ranking or reputation. This one can disclose one person's data to another.
It also leaves no trace: the application never served the second request, so nothing in its logs records the event.
The affected window is however long the entry lived, multiplied by however many callers requested that URL.
And the fix is a header, which makes the ratio of consequence to effort unusually lopsided.
Frequently asked questions
- Does an Authorization header stop caches storing the response?
- For some caches, by default. Not for all, and the rules differ. Relying on it means relying on every intermediary implementing the same default — an explicit directive does not.
- Why did this never cause a problem before we added a CDN?
- Because the directive was only ever obeyed by browsers, which keep responses to themselves. Adding a shared cache is what gives a wrong directive somewhere to do damage.
- Is no-store always the right answer?
- For user-specific data, yes. For public reference data it throws away the cheapest performance the API has. The two need different directives, not one policy.
Sources
Related
VeriFixScan crawls a site and applies its checks to every page it reaches, keeping the evidence behind each finding. Scanning one website is free.
Scan a website