Tools

API cache policy checker

An API cache policy check reads the Cache-Control header on the API responses observed and looks for one specific mistake: a response carrying data about one user, sent with a directive that allows a shared cache to store it. A CDN, a reverse proxy or a corporate proxy obeying that directive will serve the stored copy to the next caller of the same URL — which is how one account's data reaches another account without anything being compromised.

The distinction the check rests on

A private cache is the browser's own store. What it keeps is only ever handed back to the person it belongs to.

A shared cache is anything between the two: a CDN edge, a reverse proxy, a gateway. What it stores is handed to whoever asks for the same URL next.

Cache-Control decides which of the two may keep a response. `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, it is which cache is being given permission — and for a user-specific payload the answer has to exclude the shared one.

What a clean result looks like

Endpoints returning user-specific data carrying `no-store`, or `private` with a short lifetime where a browser cache is genuinely useful.

Endpoints returning public reference data carrying a real `max-age`, because those are exactly the responses a shared cache should be storing.

Authenticated responses that are cacheable also carrying `Vary: Authorization`, so a cache cannot key two different callers to the same entry.

And an explicit directive on every API response. An absent Cache-Control is not neutral: it leaves the decision to a cache's own heuristics.

What a problematic result looks like

A path that plainly returns account-specific data — a profile, an order list, a session — answered with `public` or with a bare `max-age`.

A long `max-age` on data that changes on user action, which produces the support ticket where a change is saved and the interface keeps showing the old value.

An authenticated response cacheable without `Vary: Authorization`, which is the pairing that lets one caller's entry be served to another.

No Cache-Control at all on an API. The heuristics that then apply were designed for documents, not for endpoints.

Why an external test finds this and a local one does not

In development there is usually no shared cache in the path, so a wrong directive has no visible consequence and nothing fails.

The consequence appears only once a CDN or proxy is in front of the service, which is typically true in production and typically not true anywhere else.

Reading the header from outside is what shows the directive that actually reached the network, after every layer that may have rewritten it.

What VeriFixScan uses

`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 within the discovered inventory.

When to run it

The day a CDN or a reverse proxy is put in front of the API, which is when directives that never mattered start being obeyed by something that serves other people.

After adding an endpoint that returns per-account data, since a new route inherits whatever default the framework applies rather than a decision anyone made.

When a support report describes someone seeing data that is not theirs, because a shared cache serving a stored copy produces exactly that symptom with no trace in the application logs.

After a gateway configuration change, as header rewriting at that layer silently replaces whatever the application sent.

Frequently asked questions

Is no-store always the right answer for an API?
No. It is right for user-specific data. Public reference data benefits from being cacheable, and forcing no-store everywhere throws away the cheapest performance the API has.
Does an Authorization header stop a cache from storing the response?
Not on its own for every cache. The reliable answer is an explicit directive — `private` or `no-store` — plus `Vary: Authorization` wherever an authenticated response is cacheable at all.
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.

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