API responses include appropriate cache headers
Why it matters
Authenticated API endpoints without explicit Cache-Control: no-store headers can be cached by a shared CDN or proxy, serving one user's private data — billing details, profile information, personal records — to the next user who hits the same cache key. This maps to CWE-524 (use of cache-containing sensitive information) and OWASP A02:2021 (cryptographic failures in data protection). Mutation endpoints cached by a CDN can also return stale success responses to clients while the actual operation does not re-execute.
Severity rationale
Low because most CDN configurations default to not caching JSON API responses, but any misconfiguration exposes authenticated user data to other users sharing the same cache.
Remediation
Add explicit Cache-Control headers in app/api/ route responses based on endpoint type. In Next.js App Router, set them in the Response constructor:
// Mutation endpoints and authenticated user-data endpoints
return new Response(JSON.stringify(data), {
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-store',
},
})
// Public static data endpoints (e.g., config, pricing)
return new Response(JSON.stringify(data), {
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'public, max-age=300, stale-while-revalidate=600',
},
})
Alternatively, set Cache-Control: no-store for all /api/ routes in next.config.ts headers config and override only where caching is intentional.
Detection
- ID:
api-cache-headers - Severity:
low - What to look for: Enumerate all API route handlers. For each, count whether cache-control headers are present. Look for:
Cache-Controlheaders in route responses, Next.jsrevalidateconfig on API routes, platform-level caching config. Specifically check: (1) Do mutation endpoints (POST/PUT/PATCH/DELETE) includeCache-Control: no-storeor equivalent to prevent caching? (2) Do cacheable GET endpoints (e.g., public config, static lists) include appropriate cache directives? (3) Do authenticated user-specific endpoints includeCache-Control: privateorno-store? - Pass criteria: At least 100% of mutation endpoints have explicit
no-storeorno-cacheheaders. Ideally, GET endpoints that return public static data use appropriatemax-ageorstale-while-revalidateheaders. - Fail criteria: Mutation endpoints have no cache headers and are accidentally cached by CDN or browser; or authenticated endpoints use
publiccache directives (exposing user data to shared caches). - Skip (N/A) when: The project has no API routes, or the API is entirely behind an auth gateway that sets cache headers uniformly. Signal: all routes are authenticated and middleware sets
Cache-Control: no-storeon all/api/routes. - Detail on fail: Note the cache header issues (e.g., "POST /api/user/update has no Cache-Control header; authenticated GET /api/dashboard/stats returns public cache directives"). Max 500 chars.
- Remediation: Add explicit cache headers in
app/api/route responses based on their characteristics. For any route that mutates data:Cache-Control: no-store. For any route returning user-specific data:Cache-Control: private, no-store. For public GET endpoints with static or infrequently changing data:Cache-Control: public, max-age=300, stale-while-revalidate=600. In Next.js App Router, set these in the route handler response:return new Response(JSON.stringify(data), { headers: { 'Cache-Control': 'no-store' } }). Incorrect caching of authenticated endpoints can cause user data leakage through shared CDN caches.
External references
- cwe · CWE-524 — Use of Cache Containing Sensitive Information
- owasp:2021 · A02 — Cryptographic Failures
- iso-25010:2011 · performance-efficiency.resource-utilization — Resource Utilization
Taxons
History
- 2026-04-18·v1.0.0·Initial import from saas-api-design·automated