CWE-525 (Information Exposure Through Browser Caching) is the formal classification, but the concrete failure is simpler: if a user-specific API response is cached at the CDN edge without Cache-Control: private, the next user who hits the same edge node may receive another user's private data. This is a data-exposure bug masquerading as a caching misconfiguration. ISO 25010:2011 time-behaviour flags the caching intent; the security consequence is personally identifiable or account-sensitive data served cross-user from an edge cache.
Medium because edge-caching personalized data exposes user-specific information to other users, but exploitation requires the attacker to hit the same edge node shortly after the victim — reducing but not eliminating real-world risk.
Every API route that returns user-specific data must set Cache-Control: private, no-cache. Verify this is applied at the route level, not overridden by a global CDN header rule.
// app/api/user/profile/route.ts
export async function GET(request: Request) {
const profile = await getUserProfile(request)
return Response.json(profile, {
headers: { 'Cache-Control': 'private, no-cache' },
})
}
ID: performance-deep-dive.caching-cdn.edge-cache-personalization
Severity: medium
What to look for: Count all API endpoints returning personalized/user-specific data. Enumerate their Cache-Control headers. Examine API endpoints and data-fetching patterns for personalized content (user-specific dashboards, profiles, private data). Check whether these endpoints are excluded from CDN caching via Cache-Control: private or other mechanisms.
Pass criteria: Personalized/authenticated endpoints use Cache-Control: private or are not cached at all. Public content is cached at edge. Browser cache and edge cache strategies are separated. 100% of personalized endpoints must use Cache-Control: private.
Fail criteria: Personalized endpoints are cached at edge without user differentiation, causing one user's data to be served to another. Or all endpoints use the same caching strategy regardless of personalization.
Skip (N/A) when: The application has no personalized content or all content is behind authentication.
Cross-reference: For stale-while-revalidate on public endpoints, see stale-while-revalidate-apis.
Detail on fail: "User dashboard endpoints cached with Cache-Control: public — user A's data served to user B from edge cache" or "All endpoints use same max-age=600 regardless of whether they are public or user-specific"
Remediation: Mark personalized content as non-cacheable at edge using Cache-Control: private, no-cache.
// app/api/user/dashboard/route.ts — private cache
return Response.json(userData, { headers: { 'Cache-Control': 'private, no-cache' } })