Next.js Curriculum
HardDebugging & Traps100% Free35 min read

Cache Poisoning & Cross-User Data Leaks in Shared Environments

The 30-Second Interview Answer

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
HTTP Caching headers (public vs private)Data Cache mechanicsWeb security fundamentals

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();
10
11 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

Does Next.js 15+ no-store default help prevent this bug?

Yes! Next.js 15 changed fetch() to default to `no-store`, significantly reducing accidental cache poisoning.

You can cache it in user-specific stores (like Redis keyed by userId), but never in a public shared Full Route Cache.

No. `React.cache()` is strictly scoped to the lifetime of a single HTTP request and is destroyed once the response finishes.