Safeguard
Security Guides•

Reverse proxy caching and logged-in pages: avoid leaking responses

How shared caches in a proxy or CDN can serve one user's page to another, and which Cache-Control values (private, no-store, no-cache, public) prevent it.

Safeguard Security Team
3 min read

A shared cache such as a proxy or CDN stores one response and reuses it for many users. If a personalized page is cacheable, it can be served to the wrong person. Mark personalized responses private, or no-store for sensitive ones, and test through the proxy, not just against the application.

What the directives mean

From the MDN reference for Cache-Control:

  • no-store: no cache of any kind, private or shared, should store the response.
  • no-cache: the response can be stored but must be validated with the origin before each reuse. It does not mean do not cache. If you mean do not store, use no-store.
  • private: the response can be stored only in a private cache, such as a browser. MDN warns that if you forget private on personalized content, a shared cache can store it and reuse it for multiple users, which can leak personal information.
  • public: the response can be stored in a shared cache. Responses to requests with an Authorization header must not be stored in a shared cache, but public causes such responses to be stored.

That last point is the dangerous one. Adding public to an authenticated response overrides a protection. s-maxage and must-revalidate also unlock shared caching of such responses, per MDN.

Common ways this goes wrong

  1. Missing header on a dynamic page. The framework sends no Cache-Control, and a proxy rule caches by file extension or path.
  2. A blanket cache rule for a path prefix that includes account pages.
  3. Cache key ignores a header that changes content. If the page differs by cookie or language and the key does not include it, one variant is served to all.
  4. Conflicting directives. MDN notes the most restrictive should win in theory, and that private, no-cache, no-store, max-age=0, must-revalidate is equivalent to no-store. For a clear policy, send no-store alone on sensitive pages.
  5. Error pages and redirects cached with a user specific location.

A safe default

  • Authenticated pages and API responses with user data: Cache-Control: private, no-store or just no-store for high sensitivity.
  • Static assets with content hashes in the name: long max-age, public.
  • Everything else: set it deliberately, not by accident.

First check whether your proxy caches at all. If it does, look at the cache key and at whether the cache respects the backend's Cache-Control and Set-Cookie. In Caddy, the header directive can set or strip headers, and its match option can target specific status codes.

How to test

curl -sI -H "Cookie: session=USER_A" https://example.com/account | grep -i -E 'cache-control|age|x-cache'
curl -sI https://example.com/account | grep -i -E 'cache-control|age|x-cache'

An Age or hit indicator on a personalized URL, or a second request without credentials returning content, means the cache is serving it. Test with two separate accounts too. Related cookie settings are in secure cookie flags.

Where Safeguard fits

Safeguard does not configure caches. It covers dependency risk, SBOMs and policy gates for the software behind them, with a free scan at https://gold.safeguard.sh/scan.

Quick check

Does no-cache mean the response is never stored?

No. It can be stored but must be revalidated before reuse. Use no-store to prevent storing.

Why is adding public to an authenticated response risky?

It allows shared caches to store a response that would otherwise be excluded, so other users may receive it.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.