The Ravens account page is returned with no-store, but the cache stores any path ending in a static extension like .css, and loose routing accepts any suffix after /account. Make the victim open a link like /account/logo.css and the cache stores their data under a static-looking path you then read.
A page with a field for the crafted path, a button to make the admin victim open it, and a button for you to fetch it. Routing returns the account data for any suffix after /account, and the cache stores anything ending in .css.
Deceive the cache into storing admin data under a static path, then read it.
The cache stored admin data under a static path and you read it.
Why the solution worked
The caching decision relied on the path extension, not the response headers, and loose routing conflated a static-looking path with a private page. The fix is the cache honouring caching headers and routing rejecting non-existent suffixes.
The root cause
The cache stores by extension, ignoring no-store, and routing accepts a suffix after the private path. The root is a conflict between the cache rule and the response headers.
How the code is fixed
Make the cache honour caching headers, not the path extension, and tighten routing.
Vulnerable code
// Cache rule: store anything ending in .css — ignores the no-store on /account.
if (/\.css$/.test(path)) cache.store(path, response);
Safe code
// Honour the response: never cache a response marked no-store, whatever the path looks like.
if (response.headers['cache-control']?.includes('no-store')) return; cache.store(path, response);
Impact in the real world
Cache deception leaks whole private pages to any attacker the moment the victim opens a crafted link, and has hit major services.
How defenders detect it
Defenders make the cache honour caching headers not the extension, tighten routing, and alert on private paths with static extensions.