ASVS 5.0 V14.2.5 is a Level 3 requirement. Caches must store only responses that have the expected content type for the resource and contain no sensitive, dynamic content. A request for a file that does not exist must return 404 or 302, not some other valid page. Together these stop web cache deception.
Why it matters
Web cache deception works like this. The attacker gets a victim to open /account/profile/nonexistent.css. The application, being lenient about extra path segments, returns the real profile page. The CDN sees a .css path, decides it is static, and stores the response. The attacker then requests the same URL and reads the victim's page from the cache.
Two small behaviors combine into a data leak: a forgiving router and a cache that trusts extensions.
How to implement it
Fix both halves.
Make the application strict about paths:
# Flask: no catch all that falls back to a dynamic page
@app.route("/account/profile") # exact match only
@login_required
def profile():
return render_template("profile.html")
Unknown paths, including /account/profile/x.css, should fall through to 404.
Make the cache respect headers and content type:
Cache-Control: private, no-store
on every dynamic response, and configure the cache to key off Cache-Control rather than file extension. Restrict caching rules to explicit static directories:
location /static/ {
expires 7d;
add_header Cache-Control "public";
}
location / {
add_header Cache-Control "private, no-store";
}
Some CDNs can also verify that the response content type matches the extension before storing. Turn that on if yours supports it.
Consider path normalization differences: if the cache treats ;, encoded slashes or dots differently from the origin, craft tests around them.
How to test it
- While logged in, request
/account/profile/test.cssand/account/profile%2Ftest.js. Expect 404 or a redirect. - If a 200 comes back, check the headers: it must carry
no-storeorprivate. - In a private window, request the same path. You must not see account content.
- Look at the cache status header on repeated requests to confirm dynamic paths show miss or bypass.
Common mistakes
- A single page application fallback that returns the app shell with 200 for any path.
- Cache rules based only on file extension.
- Origin headers that say
publicon personalized pages by default. - Testing one CDN edge but not the second layer of caching behind it.
Quick check
Why does returning 404 matter for the fix?
If the origin returns the real page for a made up static looking path, there is something dynamic for the cache to wrongly store. A 404 leaves nothing sensitive in the response.