- Next.js
- Debugging & Production Anti-Patterns
- Cache Poisoning & Cross-User Data Leaks in Shared Environments
Cache Poisoning & Cross-User Data Leaks in Shared Environments
A catastrophic security vulnerability occurs in Next.js when user-specific private data (e.g. account balances, profile emails, auth tokens) is cached in the shared server Data Cache or Full Route Cache. Subsequent visitors then receive the first user's cached private information from the CDN or server cache without ever hitting the database.
Learning Objectives
Identify critical cache poisoning vulnerabilities where private user data is cached globally.
Enforce strict cache boundaries for authenticated and private data.
Audit fetch request options ({ cache: "no-store" }) and cacheTag scopes.
Configure Cache-Control headers to ensure CDNs never store authenticated responses (private, no-cache).
Prerequisites & Background Knowledge
Why Interviewers Care
The highest-severity vulnerability possible in modern web applications. Interviewers assess your vigilance regarding multi-tenant and personal data safety.
The Challenge
Explain how cache poisoning occurs in Next.js App Router when fetching authenticated user data. Demonstrate the vulnerable code and refactor it into an air-tight private data fetching pattern.
Code Architecture & Implementation
1// VULNERABLE COMPONENT: DO NOT USE IN PRODUCTION!2export async function VulnerableUserProfile({ userId }: { userId: string }) {3 // CRITICAL BUG: This fetch caches the result globally in the shared Data Cache!4 // When User A views this page, User A's private email and balance are cached.5 // When User B arrives, User B is served User A's cached private data!6 const res = await fetch(`https://api.backend.com/users/${userId}`, {7 next: { revalidate: 3600 }, // SHARED CACHE ACROSS ALL USERS!8 });9 const user = await res.json();1011 return (12 <div>13 <h2>User Profile</h2>14 <p>Email: {user.email}</p>15 <p>Balance: ${user.balance}</p>16 </div>17 );18}
Architectural Explanation
The server Data Cache stores responses keyed by URL and query parameters. If a Server Component fetches private user data using a cached fetch (revalidate: 3600 or "use cache" without user-specific keys), Next.js caches that response on server disk/Redis. When any other user requests that page, the server returns the cached payload containing the first user’s sensitive data. Private data must always be fetched with cache: "no-store", or scoped strictly to that request using React.cache() (which is request-scoped and never shared across users).
Common Mistakes & Anti-Patterns
Using `revalidate: 300` on endpoints that return authenticated user profiles or order histories.
Setting HTTP headers `Cache-Control: public, s-maxage=3600` on authenticated pages, causing Cloudflare or AWS CloudFront to serve user A's page to user B.
Caching "use cache" functions without including the tenant or user ID in the cache key.
Interview Follow-Up Traps
The interviewer asks: "What HTTP header must you set to prevent CDN caching for authenticated users?" Ensure the response sends `Cache-Control: private, no-cache, no-store, must-revalidate`.
Frequently Asked Questions
Can you cache user data if you include the userId in the cache tag?
You can cache it in user-specific stores (like Redis keyed by userId), but never in a public shared Full Route Cache.
Does React.cache() risk cross-user data leakage?
No. `React.cache()` is strictly scoped to the lifetime of a single HTTP request and is destroyed once the response finishes.
