Web cache deception
How CDNs can cache private HTML under public URL shapes, and what frontend plus platform teams should set on authenticated pages.
Web cache deception tricks a cache into storing a private response under a public URL shape. The attacker then fetches that URL and reads another user's content.
It shows up when CDNs or reverse proxies key on the path extension, while the origin ignores the junk suffix and still returns the authenticated page. Frontend routing and static asset conventions make this easy to miss.
A typical frontend-shaped exploit path
Victim is logged in. Attacker sends them https://app.example.com/account/settings/evil.css. The cache thinks it is a static CSS file. The origin still renders the HTML settings page for the victim session and the cache stores it. Attacker requests the same URL without auth and receives the cached private HTML.
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(req: NextRequest) {
const response = NextResponse.next();
if (req.nextUrl.pathname.startsWith("/account")) {
response.headers.set(
"Cache-Control",
"private, no-store, max-age=0, must-revalidate"
);
}
return response;
}
Cache-Control: private, no-store, max-age=0, must-revalidate
Pragma: no-cache
// Risky mental model
// Cache: path ends with .css => public, ignore cookies
// Origin: /account/settings/* still serves private HTML
// Safer posture
// - Exact routes for private pages; reject junk suffixes
// - Cache only real static asset directories (/_next/static, /assets)
// - Bypass cache when Cookie or Authorization is present
What frontend and platform teams should do together
Private App Router pages and account dashboards need private cache headers. Static asset directories should be real static files, not catch-all app routes that still run session logic.
On Next.js and similar stacks, be careful with middleware rewrites and trailing path segments. If the framework serves /profile/foo.js as the profile page, your CDN rules must not treat it like a public script.
- no-store for authenticated HTML.
- Exact routing for private pages.
- CDN rules aligned with origin behaviour, not only file extensions.
- Do not rely on Vary: Cookie alone. Prefer not caching private responses.