{"slug":"edge-cache-data-bleed-review","title":"edge-cache-data-bleed-review","summary":"Statically review Next.js App Router caching surfaces -- route-level revalidate exports, cache-boundary directives on server functions reading cookies(), generateStaticParams on personalized routes, and Cache-Control/Vary response headers -- for defects that let one user's authen","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-10-05T21:52:12.201577Z","repo":{"url":"https://github.com/VincentChuWaiChow/vanguard-frontier-agentic","stars":24,"forks":3,"license":"Apache-2.0","updatedAt":"2026-10-05T13:00:24Z"},"bodyHtml":"<hr>\n<h2>name: edge-cache-data-bleed-review\ndescription: Statically review Next.js App Router caching surfaces -- route-level revalidate exports, cache-boundary directives on server functions reading cookies(), generateStaticParams on personalized routes, and Cache-Control/Vary response headers -- for defects that let one user's authenticated response be cached and served back to a different user.\nallowed-tools: Read Grep Glob\nmetadata:\nauthor: \"github: VincentChuWaiChow\"\nversion: \"0.1.0\"\nupdated: \"2026-07-03\"\ncategory: security</h2>\n<h1>Edge Cache Data-Bleed Review</h1>\n<h2>Purpose</h2>\n<p>Review Next.js App Router pages, server functions, Route Handlers, and response headers for the concrete caching-layer defect this skill is scoped to: a per-user, session-derived response getting written into a cache shared across requests (ISR, <code>'use cache'</code>, or a CDN/proxy edge cache) and then replayed to a different user. This skill exists so the review stays anchored to the documented caching primitives Next.js exposes for exactly this problem -- <code>revalidate</code>, <code>'use cache: private'</code>, <code>dynamic = 'force-dynamic'</code>, and the <code>Cache-Control</code>/<code>Vary</code> response headers -- instead of drifting into a general \"caching performance review\" of ISR tuning, <code>fetch</code> cache options, or CDN cost optimization with no data-exposure angle.</p>\n<h2>When to use</h2>\n<p>Use this skill when the user asks to:</p>\n<ul>\n<li>review a page or layout under <code>app/</code> that reads <code>cookies()</code> (or another per-request/per-user API) and also carries a route-level <code>revalidate</code> export,</li>\n<li>assess whether a server function or Server Component that reads <code>cookies()</code> needs <code>'use cache: private'</code>,</li>\n<li>review a dynamic route using <code>generateStaticParams</code> where the generated params are user or account IDs, to check whether the route is safely dynamic or is silently serving a shared, revalidate-windowed cache to authenticated users,</li>\n<li>audit a Route Handler's or <code>getServerSideProps</code>'s response headers (<code>Cache-Control</code>, <code>Vary</code>) for a page or API response that includes session-, cookie-, or account-derived data,</li>\n<li>perform a pre-launch security review of a Next.js app's caching configuration for cross-user data bleed.</li>\n</ul>\n<p>Do not use this skill for:</p>\n<ul>\n<li>ISR/<code>fetch</code>-cache performance tuning, <code>cacheLife</code>/<code>stale-while-revalidate</code> timing choices, or CDN cost/latency optimization with no user-specific-data angle -- those are performance concerns, not this skill's data-exposure scope,</li>\n<li>a purely client-side <code>localStorage</code>/<code>sessionStorage</code>/in-memory cache with no server-side or CDN-shared cache layer -- browser-local storage is isolated per browser profile and is out of scope for this skill's cross-user cache-bleed concern,</li>\n<li>a bug that requires live traffic reproduction (actually observing User B receive User A's cached response from a deployed CDN) to prove exploitation -- static analysis proves the structural risk, not that it has already been exploited in production.</li>\n</ul>\n<h2>Context7 Documentation Protocol</h2>\n<ul>\n<li>Resolve the Next.js library ID with <code>resolve-library-id</code> (matched result: <code>/vercel/next.js</code>) before citing any <code>revalidate</code>, <code>'use cache: private'</code>, <code>generateStaticParams</code>, <code>dynamic</code> route-segment, or header-caching behavior claim.</li>\n<li><code>/vercel/next.js</code> and <code>/websites/nextjs</code> are high-reputation sources covering the App Router's caching directives (<code>'use cache'</code>, <code>'use cache: private'</code>), route segment config (<code>revalidate</code>, <code>dynamic</code>), and header-based caching (<code>Cache-Control</code> in Route Handlers and <code>getServerSideProps</code>) directly from the framework's own docs and source. Use <code>query-docs</code> against them to confirm exact directive syntax and documented per-user-vs-shared cache semantics before writing a finding.</li>\n<li>The <code>Vary</code> header's role in CDN/proxy cache-key selection is general HTTP caching semantics, not a Next.js-specific API -- Context7 against <code>/vercel/next.js</code> will not reliably surface it. Ground a <code>Vary</code> finding against the <code>official_docs</code> MDN/HTTP-standard URL in this skill's <code>metadata.json</code> instead and label the claim <code>documentation-based</code>.</li>\n<li>Read <code>package.json</code> first to confirm the Next.js major version and whether the app uses the App Router (where <code>'use cache: private'</code>, <code>revalidate</code>, and <code>dynamic</code> route segment config apply) or the Pages Router (<code>getServerSideProps</code>/<code>getStaticProps</code>, which use a different, older caching model) -- do not apply App Router directive syntax to a Pages Router codebase or vice versa.</li>\n<li>If Context7 is unavailable, fall back to the <code>official_docs</code> URLs in this skill's <code>metadata.json</code> and label the claim <code>documentation-based, unverified against current release</code>.</li>\n</ul>\n<h2>Lean operating rules</h2>\n<ul>\n<li>All findings in this skill's scope default to HIGH severity, except an incomplete <code>Vary</code> header alone (no other caching misconfiguration present), which defaults to MEDIUM-to-HIGH depending on reachability. This is a security-scoped skill: do not downgrade a structural cross-user cache-bleed risk to MEDIUM just because it has not been observed exploited yet -- the risk is in the caching structure, not in whether someone has already hit it.</li>\n<li>Trace every finding to a concrete file:line and a concrete data-flow path. A finding that says \"this route might leak data between users\" without showing the specific <code>revalidate</code> export, the specific <code>cookies()</code> read it coexists with, or the specific response header value is not a valid finding -- it is a guess.</li>\n<li>Flag any route or layout that combines a route-level <code>export const revalidate = N</code> with a <code>cookies()</code> read (directly, or through a server function it calls) and has no <code>'use cache: private'</code> isolating that per-user lookup. <code>revalidate</code> governs a cache entry shared by every request that hits the route within the window; a personalized <code>cookies()</code>-derived render sharing that entry is the core defect this skill exists to catch.</li>\n<li>Flag any async function that reads <code>cookies()</code> (or another per-request runtime API) to produce a per-user result and does not declare <code>'use cache: private'</code> as its own cache boundary. Do not accept \"there's no <code>revalidate</code> export nearby so it's probably fine\" -- a future refactor that wraps the call site in any shared cache scope re-introduces the bleed silently; the safe pattern is the function declaring its own privacy boundary, not the absence of a nearby cache export.</li>\n<li>Flag a dynamic route whose <code>generateStaticParams</code> enumerates user- or account-scoped IDs (e.g. <code>userId</code>, <code>accountId</code>, <code>orgId</code>) when the route also carries a <code>revalidate</code> export and the page renders authenticated, per-user data. The safe idiom is <code>export const dynamic = 'force-dynamic'</code> on that route with no <code>generateStaticParams</code>/<code>revalidate</code> pairing for the authenticated path.</li>\n<li>Flag any <code>Response</code>/<code>NextResponse</code> (Route Handler) or <code>getServerSideProps</code> <code>res.setHeader</code> call that sets <code>Cache-Control: public</code> (or omits <code>Cache-Control</code> while sitting behind a CDN that defaults to caching) on a response whose body is derived from <code>cookies()</code>, a session token, or other per-user request context. The fix is <code>Cache-Control: private</code>, or omitting an explicit header and relying on Next.js's own dynamic-rendering default (<code>private, no-cache, no-store, max-age=0, must-revalidate</code>).</li>\n<li>Flag any response that sets an explicit <code>Cache-Control: public</code> (or otherwise CDN-cacheable) header on user-specific data and also sets a <code>Vary</code> header that does not include <code>Cookie</code> (or whatever header actually carries the session identifier). A CDN keys its cache only on the headers named in <code>Vary</code>; without <code>Cookie</code> present, requests from two different users are treated as cache-equivalent.</li>\n<li>Never execute, build, or run application code, and never send live requests, as part of this review; this is a static-review skill (Read/Grep/Glob only).</li>\n<li>Load only the reference needed for the concern in scope.</li>\n</ul>\n<h2>References</h2>\n<p>Load these only when needed:</p>\n<ul>\n<li><a href=\"references/workflow-and-output.md\">Review workflow and findings contract</a> -- use for the step-by-step review procedure, the per-defect decision tree, and the required output shape.</li>\n<li><a href=\"references/caching-directives-and-route-config.md\">Caching directives and route segment config</a> -- load when reviewing <code>revalidate</code>, <code>'use cache: private'</code>, <code>generateStaticParams</code>, or <code>dynamic</code> route segment config.</li>\n<li><a href=\"references/cache-control-and-vary-headers.md\">Cache-Control and Vary response headers</a> -- load when reviewing a Route Handler's or <code>getServerSideProps</code>'s response headers. Includes the MDN HTTP-caching grounding reference for <code>Vary</code>; load that citation only when a <code>Vary</code> finding is actually present.</li>\n</ul>\n<h2>Response minimum</h2>\n<p>Return, at minimum:</p>\n<ul>\n<li>the route(s), server function(s), and/or response-header call sites in scope,</li>\n<li>ranked findings with file:line evidence, defect category (<code>revalidate-cookie-bleed</code>, <code>missing-private-cache-boundary</code>, <code>static-params-auth-bleed</code>, or <code>header-cache-bleed</code>), the concrete data-flow trace (the <code>revalidate</code>/<code>generateStaticParams</code>/<code>dynamic</code> config and the <code>cookies()</code> read it coexists with, or the header value and the per-user data it exposes), and a fix sketch matching Next.js's documented pattern,</li>\n<li>for every finding involving a <code>cookies()</code>-derived value, an explicit statement of whether <code>'use cache: private'</code> (or an equivalent per-user isolation boundary) is present on the traced path -- never approve on the assumption one exists elsewhere,</li>\n<li>evidence level per finding (<code>repo evidence</code>, <code>documentation-based</code>, or <code>inference</code>), with structural risk findings explicitly labeled as structural risk, not as confirmed-exploited,</li>\n<li>verdict (approve / approve-with-notes / block),</li>\n<li>open questions or scope the review could not cover (e.g., \"confirming an actual cross-user response replay requires a live CDN reproduction with two concurrent sessions, not static review\").</li>\n</ul>\n","files":[{"path":"metadata.json","sizeBytes":1944,"isText":true},{"path":"references/cache-control-and-vary-headers.md","sizeBytes":4007,"isText":true},{"path":"references/caching-directives-and-route-config.md","sizeBytes":4869,"isText":true},{"path":"references/workflow-and-output.md","sizeBytes":7265,"isText":true},{"path":"SKILL.md","sizeBytes":9734,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-10-05T21:58:13.770753Z","sha256":"52968C13162B031D792DD80E9D9E76B81DC346F1481AF61C3C6ABCF6F9CAD4C3","sizeBytes":11938},"review":null,"source":{"repositoryUrl":"https://github.com/VincentChuWaiChow/vanguard-frontier-agentic","path":"skills/frontend/edge-cache-data-bleed-review","license":"Apache-2.0","commit":"febe32a08e78fd06b1e466187410d673f1958d87","subtreeSha":"5CCF93FF6FDC354E3A63352F4C19125AE01BA21796EC63FC83E455DD7DF78810","lastSyncedAt":"2026-10-05T21:51:58.639905Z"},"reviewedAt":"2026-10-05T22:10:37.743383Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/VincentChuWaiChow/vanguard-frontier-agentic/tree/master/skills/frontend/edge-cache-data-bleed-review"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install vincentchuwaichow-vanguard-frontier-agentic@llmmart"},{"target":"git","command":"git clone https://github.com/VincentChuWaiChow/vanguard-frontier-agentic.git"}]}