nextjs-app-router-data-fetching-review
Statically review Next.js App Router Server/Client Component boundaries and Server Action data mutations for correct data-fetching placement, bundle-leak risk, and authorization-trust integrity, escalating client-trusted authorization to a security finding rather than a style not
Install
npx skills add https://github.com/VincentChuWaiChow/vanguard-frontier-agentic/tree/master/skills/frontend/nextjs-app-router-data-fetching-review
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install vincentchuwaichow-vanguard-frontier-agentic@llmmart
git clone https://github.com/VincentChuWaiChow/vanguard-frontier-agentic.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole vincentchuwaichow/vanguard-frontier-agentic collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Next.js App Router Data Fetching Review
Purpose
Review the Server Component / Client Component boundary ('use client' placement and its import graph) and Server Action ('use server') authorization logic in a Next.js App Router codebase, without re-litigating rendering mode, fetch() caching, or component decomposition in every response. This skill exists so bundle-leak risk and Server Action authorization-trust defects stay the focus, and so those adjacent concerns stay out of scope.
When to use
Use this skill when the user asks to:
- review a
'use client'/'use server'boundary in a diff or PR, - determine whether a Server Action correctly authorizes its caller,
- investigate a report that server-only code or a secret appears to be reaching the browser bundle.
Do not use this skill for:
- pure UI/styling changes with no data-fetching or boundary change,
- Pages Router API routes (
pages/api/*) — different security model, not Server Actions; use general API-route review instead, - rendering-mode selection (static/ISR/dynamic) or
fetch()cache-configuration review — that isnextjs-rendering-caching-review, - component decomposition or state-placement review with no boundary or Server Action involved — that is
react-component-architecture-review.
Context7 Documentation Protocol
- Resolve
/vercel/next.jswithresolve-library-idbefore citing any Server Component restriction or Server Action security claim. - Before asserting what is or is not safe to import into a Client Component, or what a Server Action must re-verify, read the repo's
package.jsonto confirm the installed Next.js major version, then callquery-docsscoped to that version for "Server Actions security" and "Server Components restrictions." The exact bundling/serialization rules and the availability of the Taint API (experimental_taintObjectReference/experimental_taintUniqueValue, gated behindexperimental.taintinnext.config) are version-specific. - If Context7 is unavailable, fall back to the
official_docsURLs in this skill'smetadata.jsonand label the claimdocumentation-based, unverified against current release.
Lean operating rules
- Server Components are the App Router default;
'use client'is an opt-in boundary marker at the top of a file, above imports. Once a file is marked'use client', everything it imports and directly renders is included in the client bundle — trace that import graph, do not eyeball the single file. - Treat a Client Component's import graph reaching a server-only module (a DB/ORM client, a module reading a non-
NEXT_PUBLIC_-prefixed env var, filesystem access, or any module importingserver-only) as a HIGH-severity bundle-leak finding. This is the hard security gate for this skill's boundary half. - A Server Action's arguments (including
FormData) are fully client-controlled, even though the function body runs on the server. Treat any Server Action that derives its authorization decision from its own input parameters orFormData— instead of re-deriving identity from the server-side session (cookies(), anauth()/session helper) — as a HIGH-severity Broken Access Control finding (OWASP Top Ten A01), not a style note. - Page-level or layout-level authentication/authorization checks do not extend into a Server Action invoked from that page. Each Server Action must independently re-verify session and, for resource-scoped mutations, ownership/role — absence of that re-check is a defect even if an upstream page already checked auth.
- Do not recommend converting a Client Component to a Server Component without first checking it doesn't rely on browser-only APIs, local state, effects, or event handlers — that breaks functionality, it does not fix a boundary defect.
- Do not flag every
'use client'directive as a problem. Only flag it when the boundary is placed higher than the smallest interactive leaf needs, or when its import graph leaks server-only code. - Never execute, build, or run application code as part of this review; this is a static-review skill (Read/Grep/Glob only).
- Treat any hardcoded API key, token, session secret, or credential found in a Server Action, Client Component, or example data as a HIGH-severity finding requiring immediate escalation, not a boundary note.
References
Load these only when needed:
- Review workflow and findings contract — use for the step-by-step review procedure, the bundle-leak trace method, the authorization decision tree, and the required output shape.
- OWASP A01 — Broken Access Control — load only when a Server Action authorization finding is present, to ground the finding's severity and framing.
Response minimum
Return, at minimum:
- per-boundary table (
'use client'file, traced import-graph leak or clean, evidence file:line), - per-Server-Action table (action, authorization source used, verdict),
- ranked findings with file:line, risk class, and fix,
- the Next.js major version the claims were verified against,
- verdict: approve / approve-with-notes / block,
- evidence level and open questions.
Files (vanguard-frontier-agentic)
-
references
-
owasp-a01-broken-access-control.md 4 KB
# OWASP A01 — Broken Access Control Use this reference only when a Server Action authorization finding is present, to ground the finding's severity and framing. Do not load this for bundle-leak-only findings. ## What people get wrong The naive story is: > The Server Action is protected because it only runs on the server — the client can't tamper with server-side code. That confuses **where code executes** with **what data the code trusts**. The code executes on the server; the arguments it receives (function parameters, `FormData` fields) originate entirely from the client and are attacker-controlled, whether the caller uses the rendered form or crafts a direct request bypassing the UI. OWASP's A01:2021 — Broken Access Control category exists precisely for this class of defect: access-control decisions enforced only on the client, or derived from client-supplied data, rather than re-verified server-side against a trusted source (the session). ## Why this maps to A01, not a generic bug OWASP Top Ten A01 (Broken Access Control) covers failures where a user can act outside their intended permissions. The Server Action pattern in this skill's scope produces exactly that failure class when: - **Authorization uses client-supplied identity** — the action reads a `userId` or `role` field out of its own parameters/`FormData` instead of the server session, so any caller can claim to be any user or role. - **Insecure Direct Object Reference (IDOR)** — the action re-derives the session correctly (so the caller's identity is trustworthy) but never checks that the session's user owns or may act on the specific resource ID supplied, so an authenticated user can act on another user's resource. - **Missing function-level access control** — the action performs a privileged mutation with no authorization check at all, relying on an upstream UI/page-level gate that the direct-invocation path bypasses entirely. All three are Broken Access Control, not merely "missing validation" or "input sanitization" — the defect is that the access-control decision was placed in the wrong trust zone. ## Non-negotiable framing rules - **Severity floor**: any of the three patterns above is HIGH severity. Do not downgrade because the UI "normally" prevents the bad path — Server Actions are directly invokable independent of the rendered UI, and the review must assume a caller that bypasses it. - **Evidence requirement**: cite the exact client-controlled field (parameter name or `FormData` key) being trusted, and cite the absence (or presence, for IDOR) of a resource-ownership check. - **Do not conflate with input validation**: a missing `zod`/schema validation on `FormData` shape is a data-integrity note, not an A01 finding by itself. A01 applies specifically to the authorization *decision*, not to whether the input is well-formed. - **Page-level checks do not transfer**: an `auth()` check in the page component that renders the form does not protect the Server Action. State this explicitly in the finding so the fix targets the action, not the page. ## Minimal safe fix pattern Every HIGH finding from this reference should point toward the same shape of fix: re-derive identity from the server-side session inside the action itself, and, for resource-scoped mutations, verify ownership/role against that re-derived identity before performing the mutation. The fix is inside the Server Action, not in the calling page or the client form. ## When to push back Push back if the user proposes: - "the page already checks auth, so the action doesn't need to" — reject; each Server Action is an independent invocation surface. - "we'll trust the role field the client sends since the form only shows it to admins" — reject; UI visibility is not an access control. - "IDOR checks can wait, the session check is the important part" — reject; an authenticated-but-unauthorized mutation is still Broken Access Control. Those are not shortcuts. They are the exact failure mode this reference exists to catch. -
workflow-and-output.md 9.1 KB
# Review workflow and findings contract Use this reference for the full boundary/authorization review procedure and the required output shape. ## What people get wrong The naive story is: > `'use client'` just means "this component runs in the browser too" — eyeball the file, check it doesn't obviously import a DB client, and move on. Server Actions "run on the server," so whatever they do is trusted. Wrong, on both halves. - `'use client'` marks a **module-graph boundary**, not a single-file property. Once a file has `'use client'`, every module it imports and every component it directly renders is pulled into the client bundle — including transitive imports through shared barrel files (`lib/index.ts` re-exporting both a pure util and a DB client). A file that "looks clean" can still leak a secret three imports deep. - A Server Action's function body runs on the server, but its **inputs are fully attacker-controlled**. Nothing stops a caller from invoking the action directly with crafted `FormData`, bypassing the UI, any client-side validation, and any upstream page-level auth check entirely. "It runs on the server" says nothing about whether the action re-verifies who is calling it. The review has to operate at both the **import-graph** level (boundary/bundle-leak) and the **argument-trust** level (Server Action authorization), and it must not let a passing check on one half stand in for the other. ## Workflow 1. **Confirm the Next.js major version** - Read `package.json` for the exact Next.js version. Server Component restrictions and the availability of the Taint API (`experimental_taintObjectReference`, `experimental_taintUniqueValue`, gated behind `experimental.taint`) are version-sensitive — see the Context7 Documentation Protocol in SKILL.md. 2. **Enumerate every `'use client'` file in scope** - For each, read its import list. - Trace transitively: if it imports a local module, open that module and check its imports too, until you reach either a leaf (no further local imports) or a module that imports `server-only`, reads a non-`NEXT_PUBLIC_`-prefixed environment variable, touches the filesystem, or instantiates a DB/ORM client. - Record the exact chain (file A imports file B imports file C which reads `DATABASE_URL`) — a bundle-leak finding without a traced chain is not a finding, it is a guess. 3. **Enumerate every Server Action (`'use server'`) in scope** - This includes files with a top-level `'use server'` directive and inline `async function` bodies marked `'use server'` inside a Server Component. - For each action, identify: what mutation/read does it perform, and what value gates whether it is allowed to proceed? 4. **Classify each Server Action's authorization source** - **Session-derived (correct)**: the action calls a session/auth helper (`auth()`, `verifySession()`, a `cookies()`-backed session read) and uses the identity/role returned from that call to gate the mutation. - **Input-derived (defect)**: the action reads a user ID, role, or "isAdmin"-style flag from its own parameters or from `formData.get(...)` and uses that value — not a freshly re-derived session — to gate the mutation or to decide *whose* data to act on. - **Missing (defect)**: the action performs a mutation with no authorization check at all, relying solely on an upstream page-level check that does not extend into the action. - **Resource-scoped (verify ownership, not just auth)**: even with a valid session, if the action acts on a specific resource ID (e.g. `deletePost(postId)`), confirm it checks that the session's user actually owns/may act on that resource — an authenticated-but-unauthorized mutation is an IDOR (Insecure Direct Object Reference), still Broken Access Control. 5. **Check for boundary-placement inefficiency (non-security)** - If a `'use client'` directive sits on a large subtree (e.g. an entire page or layout) where only a small leaf component actually needs interactivity/state/browser APIs, note it as a MEDIUM bundle-size finding — recommend pushing the boundary down to the smallest interactive leaf. Do not treat this the same as a traced server-only leak. 6. **Produce ranked findings** - Order by blast radius: confirmed bundle leaks and Broken Access Control findings first (HIGH), then missing resource-ownership checks, then boundary-placement/bundle-size notes (MEDIUM/LOW). ## Decision tree - A `'use client'` file's traced import graph reaches a module importing `server-only`, reading a non-`NEXT_PUBLIC_` env var, or instantiating a DB/ORM client → **HIGH: bundle-leak risk.** Cite the exact import chain. - A Server Action gates its mutation/authorization using a value taken from its own parameters or `FormData` instead of a freshly re-derived session → **HIGH: Broken Access Control (OWASP A01).** Load `references/owasp-a01-broken-access-control.md` and frame the finding per that reference. - A Server Action re-derives session correctly but never checks that the session's user owns/may act on the specific resource ID it was given → **HIGH: IDOR / Broken Access Control (OWASP A01).** - A Server Action has no authorization check at all, relying on an upstream page-level check → **HIGH: Broken Access Control (OWASP A01).** Note explicitly that page-level checks do not extend into Server Actions. - `'use client'` is placed on a subtree larger than the interactive leaf requires, with no traced server-only leak → **MEDIUM: avoidable bundle-size cost.** Recommend pushing the boundary down; do not escalate to HIGH. - A Client Component genuinely needs conversion consideration but relies on browser-only APIs, local state, effects, or event handlers → do not recommend converting it to a Server Component; note the constraint instead. ## Output contract Return: 1. Next.js major version confirmed (or explicitly noted as unconfirmed) 2. Per-boundary table: `'use client'` file | traced import-graph result (clean / leak with chain) | evidence (file:line) 3. Per-Server-Action table: action | authorization source (session-derived / input-derived / missing / resource-scoped-unverified) | verdict 4. Ranked findings, each with: - file:line evidence - risk class (bundle-leak / broken-access-control / IDOR / missing-auth-check / avoidable-bundle-size) - concrete fix, scoped to the narrowest sufficient change - severity (HIGH / MEDIUM / LOW) - evidence level (`repo evidence`, `documentation-based`, `inference`) 5. Verdict: approve / approve-with-notes / block 6. Open questions or explicitly out-of-scope items (e.g. Pages Router API routes encountered, or rendering/caching concerns deferred to `nextjs-rendering-caching-review`) ## Validation gates - Every bundle-leak finding traces the actual import chain from the `'use client'` file to the leaked server-only module — no finding asserts bundle contents without a traced chain. - Every authorization finding identifies exactly which client-controlled value (parameter name, `FormData` key) is being trusted in place of a session re-derivation. - No finding recommends converting a Client Component to a Server Component without first confirming it has no browser-only API, state, effect, or event-handler dependency. - No Broken Access Control finding is downgraded to a style note or MEDIUM severity for "code cleanliness" reasons — the security-notes hard gate in `metadata.json` applies regardless of how small the fix looks. ## Common failure modes - Flagging every `'use client'` directive as a problem regardless of whether interactivity is actually needed there. - Missing transitive imports — checking only the top-level imports of a `'use client'` file and stopping, rather than following a shared `lib/db.ts` imported through an intermediate barrel file. - Assuming Server Actions are inherently safe "because they run on the server," while ignoring that their inputs are fully client-controlled and can be invoked directly, bypassing the UI. - Treating a page-level or layout-level auth check as sufficient for a Server Action invoked from that page, without checking the action re-verifies independently. - Confusing "authenticated" with "authorized for this specific resource" — a valid session does not imply the session's user may act on the resource ID supplied. ## Adversarial checklist Before finalizing a finding, answer these: - Does a Server Action re-derive the acting user's identity from the server session, or trust an id/role passed in from the client? - Does the import graph of every `'use client'` file actually avoid server-only modules, traced transitively, not just at the top level? - Is `'use client'` placed at the smallest necessary leaf, or does it needlessly convert a large subtree? - Would this Server Action behave safely if called directly with crafted `FormData` bypassing the UI entirely? - For resource-scoped actions, does the check confirm the session's user owns/may act on the specific resource ID, not just that a session exists? If any answer is "not sure," lower the finding's confidence and label the evidence level accordingly — do not present it as a confirmed defect, except for Broken Access Control findings with clear file:line evidence of client-controlled authorization, which stay HIGH regardless.
-
-
metadata.json 1.8 KB
{ "id": "nextjs-app-router-data-fetching-review", "name": "Next.js App Router Data Fetching Review", "type": "skill", "provider": "frontend", "harnesses": [ "claude-code", "cursor", "codex", "gemini", "kiro", "other" ], "summary": "Reviews Server/Client Component boundaries and Server Action authorization trust for data-fetching correctness and security, using Next.js's own Server Components/Server Actions documentation loaded progressively and grounded via Context7 against the repo's confirmed Next.js version.", "source_type": "original", "official_docs": [ "https://nextjs.org/docs/app/building-your-application/rendering/server-components", "https://nextjs.org/docs/app/building-your-application/rendering/client-components", "https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations", "https://owasp.org/www-project-top-ten/" ], "security_notes": "A Server Action that derives an authorization decision from client-supplied form data instead of the server-side session is a Broken Access Control finding (OWASP A01) and must be escalated as HIGH, not filed as a style note. A Client Component whose import graph reaches a server-only module (DB client, non-NEXT_PUBLIC_ env access, server-only-guarded module) is a HIGH-severity bundle-leak finding. Static-review-only skill: it reads and greps component/action source but never executes, builds, or runs application code. Treat any hardcoded API key, token, or credential found in a Server Action or Client Component as a HIGH-severity finding requiring immediate escalation.", "last_verified": "2026-07-02", "path": "skills/frontend/nextjs-app-router-data-fetching-review", "author": "github: VincentChuWaiChow", "version": "0.1.0" } -
SKILL.md 5.6 KB
--- name: nextjs-app-router-data-fetching-review description: Statically review Next.js App Router Server/Client Component boundaries and Server Action data mutations for correct data-fetching placement, bundle-leak risk, and authorization-trust integrity, escalating client-trusted authorization to a security finding rather than a style note. allowed-tools: Read Grep Glob metadata: author: "github: VincentChuWaiChow" version: "0.1.0" updated: "2026-07-02" category: architecture --- # Next.js App Router Data Fetching Review ## Purpose Review the Server Component / Client Component boundary (`'use client'` placement and its import graph) and Server Action (`'use server'`) authorization logic in a Next.js App Router codebase, without re-litigating rendering mode, `fetch()` caching, or component decomposition in every response. This skill exists so bundle-leak risk and Server Action authorization-trust defects stay the focus, and so those adjacent concerns stay out of scope. ## When to use Use this skill when the user asks to: - review a `'use client'`/`'use server'` boundary in a diff or PR, - determine whether a Server Action correctly authorizes its caller, - investigate a report that server-only code or a secret appears to be reaching the browser bundle. Do not use this skill for: - pure UI/styling changes with no data-fetching or boundary change, - Pages Router API routes (`pages/api/*`) — different security model, not Server Actions; use general API-route review instead, - rendering-mode selection (static/ISR/dynamic) or `fetch()` cache-configuration review — that is `nextjs-rendering-caching-review`, - component decomposition or state-placement review with no boundary or Server Action involved — that is `react-component-architecture-review`. ## Context7 Documentation Protocol - Resolve `/vercel/next.js` with `resolve-library-id` before citing any Server Component restriction or Server Action security claim. - Before asserting what is or is not safe to import into a Client Component, or what a Server Action must re-verify, read the repo's `package.json` to confirm the installed Next.js major version, then call `query-docs` scoped to that version for "Server Actions security" and "Server Components restrictions." The exact bundling/serialization rules and the availability of the Taint API (`experimental_taintObjectReference` / `experimental_taintUniqueValue`, gated behind `experimental.taint` in `next.config`) are version-specific. - If Context7 is unavailable, fall back to the `official_docs` URLs in this skill's `metadata.json` and label the claim `documentation-based, unverified against current release`. ## Lean operating rules - Server Components are the App Router default; `'use client'` is an opt-in boundary marker at the top of a file, above imports. Once a file is marked `'use client'`, everything it imports and directly renders is included in the client bundle — trace that import graph, do not eyeball the single file. - Treat a Client Component's import graph reaching a server-only module (a DB/ORM client, a module reading a non-`NEXT_PUBLIC_`-prefixed env var, filesystem access, or any module importing `server-only`) as a HIGH-severity bundle-leak finding. This is the hard security gate for this skill's boundary half. - A Server Action's arguments (including `FormData`) are fully client-controlled, even though the function body runs on the server. Treat any Server Action that derives its authorization decision from its own input parameters or `FormData` — instead of re-deriving identity from the server-side session (`cookies()`, an `auth()`/session helper) — as a HIGH-severity Broken Access Control finding (OWASP Top Ten A01), not a style note. - Page-level or layout-level authentication/authorization checks do not extend into a Server Action invoked from that page. Each Server Action must independently re-verify session and, for resource-scoped mutations, ownership/role — absence of that re-check is a defect even if an upstream page already checked auth. - Do not recommend converting a Client Component to a Server Component without first checking it doesn't rely on browser-only APIs, local state, effects, or event handlers — that breaks functionality, it does not fix a boundary defect. - Do not flag every `'use client'` directive as a problem. Only flag it when the boundary is placed higher than the smallest interactive leaf needs, or when its import graph leaks server-only code. - Never execute, build, or run application code as part of this review; this is a static-review skill (Read/Grep/Glob only). - Treat any hardcoded API key, token, session secret, or credential found in a Server Action, Client Component, or example data as a HIGH-severity finding requiring immediate escalation, not a boundary note. ## References Load these only when needed: - [Review workflow and findings contract](references/workflow-and-output.md) — use for the step-by-step review procedure, the bundle-leak trace method, the authorization decision tree, and the required output shape. - [OWASP A01 — Broken Access Control](references/owasp-a01-broken-access-control.md) — load only when a Server Action authorization finding is present, to ground the finding's severity and framing. ## Response minimum Return, at minimum: - per-boundary table (`'use client'` file, traced import-graph leak or clean, evidence file:line), - per-Server-Action table (action, authorization source used, verdict), - ranked findings with file:line, risk class, and fix, - the Next.js major version the claims were verified against, - verdict: approve / approve-with-notes / block, - evidence level and open questions.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.