Dynamic Pages Cached After Adding .css? Troubleshoot Web Cache Deception and Path Rule Mismatches
Create Time:2026-11-04 13:35:42
浏览量
1066

A private profile card disguised as a static file approaches a CDN cache, where a shield blocks shared storage.

An account page should never enter a shared cache. Yet an altered URL with a static-looking suffix may still return the same authenticated content from the origin while the CDN treats it as a cacheable asset. This mismatch is a central condition in web cache deception: different layers interpret the path differently, allowing a private response to be stored where it can be reused.[1]

This guide is for developers and operators responsible for their own sites. Perform checks only in an authorized staging environment with test accounts and synthetic data. These examples have not been tested against your production environment. Do not send suspicious links to real users or retrieve another person's information to demonstrate impact.

Distinguish deception from an ordinary cache mistake

An ordinary configuration mistake might cache /account directly. Web cache deception instead uses a path that appears eligible for static caching but is still handled as a dynamic page, giving a private response a route into the cache.

For example, the application might ignore an appended path segment and return the account page, while the edge classifies the trailing extension as a stylesheet. Directory mappings, path delimiters, and normalization differences can create similar mismatches.[1] Adding .css does not make every application vulnerable; origin routing, edge rules, and cache keys all matter.

Do not conflate this with web cache poisoning. Poisoning commonly concerns putting attacker-influenced response content into a cache; deception concerns inducing the storage of private responses that should not be shared. Some weak configurations overlap, but the investigation has a different objective.

Establish how the origin handles unexpected paths

Create a staging account page containing only a fixed synthetic marker. Compare its canonical path with an appended path that should not exist. Check status codes, redirects, account markers, and the dynamic handler that actually serves each response.

Backend frameworks, reverse proxies, and SPA fallback rules may handle unknown paths differently. Identical-looking pages do not prove identical routing. Use application logs to establish which handler was selected.

For account, billing, and order endpoints, unexpected paths should not silently inherit private-page response semantics. Depending on the application, return a clear not-found response or a deliberate canonical redirect. Assess existing links, language routes, and client-side routing before changing behavior. Disabling all frontend routing is not an appropriate blanket fix.

Find the rule that grants cache eligibility

Export current CDN rules and review extension matches, path prefixes, cache-everything settings, edge code, and origin Cache-Control overrides. Establish their execution order rather than relying on a single bypass rule visible in the dashboard.

Granting cache eligibility based only on .css, .js, or an image suffix is risky on a site with mixed dynamic routes. A dedicated static directory or hostname with explicit routing boundaries is often easier to audit. A separate hostname alone is not a security guarantee: cookie scope, origin mapping, and response policies still need checking.

Whether cookies participate in a cache key is separate from whether a response may be shared. Adding every cookie can create many variants without establishing a safe private-response policy. Whether an unauthenticated request can retrieve the same cached object depends on the actual key and authorization flow. One cache hit does not establish exposure to every user.

Give private responses an explicit storage policy

Under HTTP caching semantics, private restricts storage by shared caches, while no-store instructs caches not to store the response. no-cache does not mean “do not save”; it requires validation before reuse.[3] For genuinely non-storable private data, a policy to evaluate is:

Cache-Control: private, no-store

This is not a recommendation to disable caching for every dynamic response. Public, user-independent data can have a separate policy. Classify the data before deciding whether shared caching is appropriate.

Confirm whether the edge is configured to ignore or override origin directives. Adding no-store in the application does not establish the actual outcome if an explicit force-cache rule changes the behavior. Nor is Set-Cookie a universal, cross-provider guarantee against caching. Check the service's documented handling and configuration.

Vary: Cookie is not a general patch for sensitive data. It can express cookie-dependent representations, but support, variant counts, and edge settings require review. An unsafe shared cache should not be kept working by multiplying variants indefinitely.

Verify cross-session reuse in an isolated test

Use two isolated test sessions. Session A signs in to a test account and requests a controlled staging path that will not share a cache entry with normal traffic. Session B requests that same path without A's cookies or Authorization value. Use synthetic markers, not names, invoices, or tokens.

Inspect response contents, status, cache-related headers, and edge logs to determine whether B receives A's marker. An Age field or vendor-specific hit indicator alone neither proves a private-data leak nor guarantees safety. Multiple proxies may add or rewrite these fields.

Likewise, an authenticated curl request may not reproduce browser behavior. Check that the tools do not share sessions automatically, follow redirects to an unrelated page, or mistake a locally retained response for a fresh edge response.

Do not assume a random query parameter isolates the test. Some cache keys ignore query strings. Establish how the test path maps to the cache key first, then remove the relevant test entries afterward. Treat HAR files, request logs, and response bodies as sensitive diagnostic material: limit access and delete them when no longer needed.

Compare path normalization across the chain

The CDN, WAF, load balancer, and application may decode or normalize paths at different stages. Compare the original request target, the path used for edge matching, and what reaches the origin. Logged paths may already be transformed and are not necessarily evidence of the original bytes.

Do not deploy a global decoding or rewriting rule simply to make every path appear consistent. Repeated decoding, incorrect delimiter handling, and encoding changes can affect signatures, routing, and access control. Compare controlled samples first, then make the smallest endpoint-specific correction.

Provider protections can add another check. Cloudflare Cache Deception Armor, for example, examines the relationship between the extension and response Content-Type as part of its cache-safety logic.[2] Consult its documented exceptions and scope. It does not replace correct routing and private-response policies, and similarly named features from another CDN should not be assumed equivalent.

A rule change does not automatically remove stored content

If exposure is suspected, first prevent affected private endpoints from entering shared storage and preserve necessary redacted evidence through the incident process. After changing origin headers or routing, establish how existing edge objects will be removed.

Invalidate according to actual cache keys, path variants, and distribution scope. If the affected object set is uncertain, the incident owner should assess broader invalidation and its origin-load implications. Avoiding a full purge must not become a reason to leave sensitive objects available.

Purging cannot recall information already downloaded. If responses contained live session tokens or other credentials, assess revocation, rotation, and user notification. Personal-data exposure without credentials may still require privacy and security handling. Deleting a cache entry does not establish that no harm occurred.

Validate private routes and legitimate static assets

Cover canonical private paths, unexpected appended paths, authenticated and unauthenticated sessions, and normal stylesheets, scripts, and images. Private responses must not be reused across users. Legitimate assets should not all be forced to the origin by an unnecessarily broad bypass rule.

Back up the existing rules and document the change order. Test in staging and limited traffic while watching cache hits, origin load, route errors, and sensitive-endpoint storage behavior. Rollback must not simply restore a known data-exposure rule. If availability suffers, keep private routes excluded from caching while adjusting public-resource rules.

The investigation should establish how the origin interprets the path, why the CDN considers it cacheable, and whether another session can reuse private content. Aligning those three pieces of evidence distinguishes cache deception from an ordinary policy mistake or accidental session sharing in the test tools.

References

Source review date: November 4, 2026. Provider capabilities and rule precedence depend on current documentation and actual configuration.

  1. PortSwigger: Web cache deception

  2. Cloudflare: Cache Deception Armor

  3. RFC 9111: HTTP Caching