Claude Cursor GitHub Copilot Skill

nextjs-server-security-review

Statically review Next.js middleware, Server Actions, next.config.js, and environment-variable files for four documented server-side defect classes -- middleware matcher exclusions that silently skip auth on Server Functions, Server Actions missing allowedOrigins CSRF protection,

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download vincentchuwaichow-vanguard-frontier-agentic-skills_frontend_nextjs-server-security-review-febe32a.zip · 12 KB
Part of vincentchuwaichow/vanguard-frontier-agentic — 293 skills

Install

skills CLI npx skills add https://github.com/VincentChuWaiChow/vanguard-frontier-agentic/tree/master/skills/frontend/nextjs-server-security-review
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install vincentchuwaichow-vanguard-frontier-agentic@llmmart
Git 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 Server Security Review

Purpose

Review Next.js middleware (middleware.ts/.js), Server Actions, next.config.js, and environment-variable files for the concrete server-side defect classes Next.js's own documentation calls out directly: middleware matcher exclusions that silently skip authorization for Server Functions, Server Actions missing allowedOrigins CSRF protection, secrets accidentally exposed to the client via a NEXT_PUBLIC_ prefix, and server-side request forgery / open-redirect risk via dangerouslyAllowLocalIP or an unvalidated rewrite destination. This skill exists so the review stays anchored to these documented defect classes instead of drifting into a general "Next.js code review" of component architecture, data fetching patterns, or styling.

When to use

Use this skill when the user asks to:

  • review middleware.ts/middleware.js and its matcher configuration for authorization gaps,
  • assess whether a Server Action is protected against CSRF, or whether next.config.js's serverActions.allowedOrigins is configured correctly for a reverse-proxy or multi-zone deployment,
  • audit .env/.env.production files or any process.env.NEXT_PUBLIC_* usage for accidental secret exposure to the client bundle,
  • review next.config.js image configuration (images.dangerouslyAllowLocalIP) or rewrites()/NextResponse.rewrite() usage for SSRF or open-redirect risk,
  • perform a pre-launch security review of a Next.js server surface (middleware, Server Actions, config, env files).

Do not use this skill for:

  • client-only React component logic with no middleware, Server Action, config, or environment-variable surface in scope — there is no server-side sink for this skill to review,
  • general Next.js performance, data-fetching-pattern, or App Router/Pages Router migration review with no security angle,
  • a bug that requires live traffic reproduction (actually triggering a CSRF request cross-origin, actually confirming an SSRF callback from a deployed image optimizer) to prove exploitation — static analysis proves the structural risk, not that it has already been exploited in production.

Context7 Documentation Protocol

  • Resolve the Next.js library ID with resolve-library-id (matched result: /vercel/next.js) before citing any middleware, Server Actions, environment-variable, or next.config.js behavior claim.
  • /vercel/next.js is a high-reputation source covering authentication, middleware, Server Actions, environment variables, and next.config.js security patterns directly from the framework's own docs and source. Use query-docs against it to confirm exact option names (serverActions.allowedOrigins, images.dangerouslyAllowLocalIP) and documented behavior (NEXT_PUBLIC_ inlining, matcher exclusion semantics) before writing a finding.
  • Read package.json first to confirm the Next.js major version and whether the app uses the App Router or Pages Router — experimental.serverActions configuration, middleware matcher conventions, and NextResponse.rewrite() APIs have shifted across major versions; do not apply App Router API names to a Pages Router codebase or vice versa.
  • 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

  • All four defect classes in this skill's scope default to HIGH severity. This is a security-scoped skill: do not downgrade a middleware-matcher authorization gap, a missing CSRF allowlist, a leaked secret, or a structural SSRF/open-redirect path to MEDIUM just because it has not been observed exploited yet — the risk is in the structure, not in whether someone has already hit it.
  • Trace every finding to a concrete file:line and a concrete data-flow path. A finding that says "this middleware might not protect everything" or "this env var might leak" without showing the specific matcher pattern, the specific serverActions config, or the specific variable declaration is not a valid finding — it is a guess.
  • Never accept a middleware matcher as sufficient authorization coverage in isolation. A matcher whose negative-lookahead pattern excludes a path (e.g. /((?!api|_next).*)) means middleware — and any auth check it performs — never runs for that excluded path; a Proxy matcher that excludes a path also skips Server Function calls on that path. Confirm the excluded path's own handlers (Server Actions, Route Handlers) independently verify the session themselves.
  • Never approve a next.config.js with a serverActions block configured for a reverse-proxy, multi-zone, or otherwise cross-origin deployment unless allowedOrigins is explicitly present in that same block. Next.js's default CSRF protection compares the Server Action request's Origin header to the Host header and rejects mismatches — a deployment where those two headers legitimately differ (reverse proxy, multi-zone) needs the allowlist or every legitimate request fails, or worse, the mismatch is worked around insecurely elsewhere.
  • Flag every environment variable whose name suggests a secret (contains KEY, SECRET, TOKEN, PASSWORD, CREDENTIAL, or equivalent) and is prefixed NEXT_PUBLIC_. Any NEXT_PUBLIC_ variable is inlined into the JavaScript bundle at build time and shipped to every client, full stop — there is no runtime gate that can retroactively hide it once bundled. The safe fix is to drop the prefix and read the value only from server-side code via process.env.API_KEY (or the equivalent unprefixed name), never from a Client Component.
  • Flag images.dangerouslyAllowLocalIP: true in next.config.js as a structural SSRF risk unless the codebase demonstrably validates every dynamic src value against a hardcoded external-hostname allowlist before it reaches the <Image> component.
  • Flag any NextResponse.rewrite() call (or rewrites() destination) whose target URL is built directly from user-controlled input (query parameters, headers, request body) with no hostname allowlist check on the resolved value before the rewrite call — this is SSRF/open-redirect via the rewrite backend, not a cosmetic routing bug.
  • 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).
  • Load only the reference needed for the concern in scope.

References

Load these only when needed:

Response minimum

Return, at minimum:

  • the middleware file(s), Server Action(s), next.config.js, and/or environment file(s) in scope,
  • ranked findings with file:line evidence, defect category (middleware-auth-gap, csrf-origin, secret-leak, or ssrf-redirect), the concrete data-flow trace (the matcher pattern and the excluded path's own auth handling, the serverActions config and its deployment topology, the environment variable declaration and its usage site, or the origin-to-sink path for the SSRF/redirect finding), and a fix sketch matching Next.js's documented pattern,
  • for every middleware-auth-gap finding, an explicit statement of whether the excluded path's own handler independently verifies the session — never approve on the assumption it does,
  • evidence level per finding (repo evidence, documentation-based, or inference), with structural risk findings explicitly labeled as structural risk, not as confirmed-exploited,
  • verdict (approve / approve-with-notes / block),
  • open questions or scope the review could not cover (e.g., "confirming actual cross-origin CSRF success requires a live cross-origin request, not static review").
Files (vanguard-frontier-agentic)
  • references
    • env-and-ssrf-surfaces.md 6.8 KB
      # Environment Variables, Image SSRF, and Rewrite/Redirect Injection
      
      Use this reference only when the review scope includes `.env*` files, `NEXT_PUBLIC_*` usage, `images.dangerouslyAllowLocalIP` in `next.config.js`, or a rewrite/redirect destination built from dynamic input. The OWASP SSRF/open-redirect citation is loaded only when such a finding is actually present — do not cite it preemptively in a review with no SSRF/redirect surface.
      
      ## What people get wrong: `NEXT_PUBLIC_` secrets
      
      The naive assumption is:
      
      > "I'll just prefix it with `NEXT_PUBLIC_` so the client component I'm writing right now can read it — I'll clean it up later."
      
      Wrong, and unrecoverable after the fact. Next.js's own documentation is direct: environment variables prefixed `NEXT_PUBLIC_` are inlined into the JavaScript bundle at build time (`documentation-based`, Next.js environment-variables guide). This is not a runtime gate that can be toggled off later — every client that has ever loaded a build containing the value has received it, in plaintext, in a bundle they can inspect via browser devtools or by fetching the JS file directly. Rotating the underlying secret afterward does not undo the exposure of the old value's existence, format, or any access it already granted before rotation.
      
      ## Officially grounded rule
      
      Non-`NEXT_PUBLIC_` environment variables are exclusively available in the Node.js server environment and are never sent to the browser (`documentation-based`, Next.js environment-variables guide). The correct pattern is:
      
      ```txt
      # .env
      API_KEY=<REDACTED_secret_value>
      ```
      
      ```js
      // Server Action or Route Handler only — never a Client Component
      export async function myServerAction() {
        const key = process.env.API_KEY
        // ...
      }
      ```
      
      ## Non-negotiable design rules
      
      ### 1. Flag by name pattern, then confirm by value shape
      
      Any `NEXT_PUBLIC_`-prefixed variable whose name contains `KEY`, `SECRET`, `TOKEN`, `PASSWORD`, `CREDENTIAL`, or an obvious equivalent is a finding regardless of the value shown in the file — even a placeholder or rotated value in a committed `.env.example` demonstrates the naming pattern will leak the real value wherever it is actually set.
      
      ### 2. A genuinely public value under `NEXT_PUBLIC_` is not a finding
      
      An analytics ID, a public feature-flag name, or a publishable (not secret) API key that the vendor itself documents as safe for client exposure (e.g., a Stripe *publishable* key, as opposed to a *secret* key) is the correct, intended use of the prefix. Do not manufacture a finding for a variable that is genuinely meant to be public — check the vendor's own documentation for whether a given key type is designed for client exposure before flagging it.
      
      ## What people get wrong: image optimization SSRF
      
      The naive assumption is:
      
      > "`dangerouslyAllowLocalIP` just lets me test with a local dev image server, it's not a real security control."
      
      The name is the warning. Enabling `dangerouslyAllowLocalIP` allows the built-in image optimizer to fetch a `src` URL that resolves to a loopback or internal-network address. Combined with any dynamic (user- or attacker-influenced) `src` value, this becomes a classic SSRF primitive: an attacker supplies a URL pointing at an internal service (a cloud metadata endpoint, an internal admin panel, a database's HTTP interface) and the server-side image optimizer fetches it on the attacker's behalf (`documentation-based`, Next.js image-configuration guide — generally not recommended due to potential SSRF risk).
      
      ## Non-negotiable design rule
      
      `images.dangerouslyAllowLocalIP: true` is a finding unless every dynamic `src` value reaching `<Image>` is validated against a hardcoded allowlist of safe external hostnames before it is used. `remotePatterns`/`domains` configuration that is itself permissive (a wildcard hostname pattern) does not clear this finding — the two controls are independent; a broad `remotePatterns` plus `dangerouslyAllowLocalIP: true` is worse, not better.
      
      ## What people get wrong: rewrite/redirect destination injection
      
      The naive assumption is:
      
      > "`NextResponse.rewrite()`/a dynamic rewrite is just routing logic, not a security-sensitive sink."
      
      Wrong when the destination is built from user-controlled input. `NextResponse.rewrite(new URL(userControlledValue, request.url))` (or an equivalent `rewrites()` destination built from request data) lets an attacker supply a URL that the rewrite forwards the request to — either an internal service (SSRF) or an external attacker-controlled host (functionally an open redirect, since the response the client ultimately sees originates from the rewritten destination).
      
      ## Non-negotiable design rule
      
      Any rewrite/redirect destination whose value traces back to user-controlled input (a query parameter, a header, a request body field) must pass through a hardcoded hostname allowlist check *before* the rewrite/redirect call — not merely be parsed with `new URL()` (which validates syntax, not safety) and passed straight through. Resolve the value to a local variable, check it against the allowlist, and only then call `NextResponse.rewrite()`/return the redirect. A destination built entirely from fixed, developer-authored literals is not a finding.
      
      ## OWASP grounding (load only when an SSRF/redirect finding is present)
      
      Server-Side Request Forgery is the vulnerability class both `dangerouslyAllowLocalIP` misuse and unvalidated rewrite destinations produce: server-side code is induced to make a request to a location the attacker chose rather than the application developer, potentially reaching internal-network resources otherwise unreachable from the public internet. The OWASP SSRF reference (listed in this skill's `official_docs`) provides the vendor-neutral grounding for why this defect class is treated as HIGH severity by default — it commonly enables internal network reconnaissance, cloud metadata credential theft, or bypass of network-perimeter controls, not merely a routing inconvenience. Cite it only in the specific finding write-up for a confirmed or suspected SSRF/redirect defect, not as boilerplate in every review.
      
      ## Verification targets
      
      - Grep every `.env*` file and any code referencing `process.env.NEXT_PUBLIC_` for a secret-shaped variable name.
      - Grep `next.config.js` for `dangerouslyAllowLocalIP` and check its value; if `true`, grep the codebase for every dynamic `<Image src=` binding and trace its origin.
      - Grep for `NextResponse.rewrite(` and `rewrites()` array entries; for each, trace the `destination`/URL argument's origin backward through variables to its source (literal, env var, or user-controlled request data).
      - Grep for a hostname allowlist check (`.includes(`, `ALLOWED_HOSTS`, or an equivalent project-specific allowlist array) and confirm its call site sits between the user-controlled origin and the rewrite/redirect call, not merely present elsewhere in the file.
      
    • middleware-and-server-actions.md 5.4 KB
      # Middleware Matcher Exclusions and Server Actions CSRF
      
      Use this reference only when reviewing `middleware.ts`/`.js`, its `matcher` configuration, or a Server Action's CSRF posture in `next.config.js`.
      
      ## What people get wrong: matcher exclusions
      
      The naive assumption is:
      
      > "Middleware runs before every request, so anything I check in middleware protects the whole app."
      
      Wrong, for any path the `matcher` excludes. A `matcher` written as a negative lookahead — e.g. `'/((?!api|_next).*)'` — is not just "skip static assets," it is "skip every path matching that lookahead, including API routes hosting Server Actions and Route Handlers." Next.js's own documentation on proxy execution order is explicit and non-negotiable on this point: a Proxy matcher that excludes a path also skips Server Function calls on that path (`documentation-based`, Next.js proxy/middleware docs). If the only auth check in the codebase lives inside middleware, and the matcher excludes `/api`, then every Server Action and Route Handler under `/api` runs with **zero** authorization enforcement from middleware — because middleware never runs for them at all.
      
      ## Non-negotiable design rules
      
      ### 1. A matcher exclusion is not evidence of anything — it is the absence of evidence
      
      Do not treat "middleware exists in this project" as sufficient. For each path the `matcher` excludes, that path's *own* handler must independently verify the session. There is no such thing as partial credit for "the matcher probably wasn't meant to exclude sensitive routes" — read the actual regex and the actual excluded paths.
      
      ### 2. Distinguish an explicit allowlist matcher from an exclusion-style matcher
      
      A `matcher` that explicitly lists the paths middleware should run on (e.g. `['/dashboard/:path*', '/admin/:path*']`) is a fundamentally different risk shape than a negative-lookahead exclusion matcher (e.g. `['/((?!api|_next).*)']`). The explicit-allowlist form makes it obvious which paths get middleware coverage and, by construction, does not accidentally exclude an API route someone forgot about. Recommend this form when a matcher-exclusion gap is found.
      
      ### 3. Trace what each Server Action actually checks
      
      For any Server Action (`'use server'` function) reachable from an excluded path, read its full body. A session check must be visible inside that function — not merely assumed because "there's an auth library imported at the top of the file." An import with no corresponding call site inside the action is not a check.
      
      ## What people get wrong: Server Actions CSRF
      
      The naive assumption is:
      
      > "Next.js handles CSRF for Server Actions automatically, so I don't need to configure anything."
      
      Half right. Next.js's built-in protection only allows `POST` requests and compares the request's `Origin` header to its `Host` header, aborting on mismatch (`documentation-based`, Next.js data-security guide). That default works for a simple same-origin deployment. It silently breaks down — or gets worked around insecurely — for any deployment where `Origin` and `Host` legitimately differ for real production traffic: an app behind a reverse proxy, a multi-zone setup where one domain fronts several Next.js apps, or a staging environment fronted by a different domain than the app's own `Host`.
      
      ## Officially grounded rule
      
      `serverActions.allowedOrigins` in `next.config.js` lets the framework compare the Server Action request's origin against an explicit, safe allowlist instead of only the automatic `Host`-header comparison:
      
      ```js
      /** @type {import('next').NextConfig} */
      module.exports = {
        experimental: {
          serverActions: {
            allowedOrigins: ['my-proxy.com', '*.my-proxy.com'],
          },
        },
      }
      ```
      
      (`documentation-based`, Next.js data-security and multi-zone guides.)
      
      ## Non-negotiable design rules
      
      ### 1. A `serverActions` block missing `allowedOrigins` is not automatically safe
      
      If the deployment is same-origin only (no reverse proxy, no multi-zone, `Origin` and `Host` always match for real traffic), omitting `allowedOrigins` is the documented safe default — do not manufacture a finding where none exists. But if the deployment topology is cross-origin (reverse proxy, multi-zone, CDN rewriting `Host`), a `serverActions` block that configures other options (`bodySizeLimit`, etc.) but omits `allowedOrigins` is the exact CSRF gap this rule exists to catch. Confirm the deployment topology before judging severity — check `next.config.js` `rewrites()`, deployment docs, or infrastructure config for reverse-proxy/multi-zone evidence.
      
      ### 2. `allowedOrigins` entries must be the actual safe origins, not a wildcard-everything pattern
      
      `allowedOrigins: ['*']` (or an equivalent catch-all) defeats the purpose of an allowlist — it accepts CSRF requests from any origin. Flag a wildcard-everything entry the same as a missing `allowedOrigins` key.
      
      ## Verification targets
      
      - Grep `middleware.ts`/`.js` for `matcher:` and inspect the pattern for a negative lookahead (`(?!...)`) versus an explicit path list.
      - Grep every file the matcher excludes for `'use server'` and read each exported action's full body for a session/auth check.
      - Grep `next.config.js` for `serverActions:` and check whether `allowedOrigins` is a sibling key inside that same object.
      - Grep deployment config / `rewrites()` / infra docs for reverse-proxy or multi-zone evidence to determine whether a missing `allowedOrigins` is actually a gap or a legitimate same-origin default.
      
    • workflow-and-output.md 6.6 KB
      # Review Workflow and Findings Contract
      
      Use this reference for the step-by-step review procedure and the required output shape. Load the other two references only for the specific defect class the middleware, Server Action, config, or environment file under review actually raises.
      
      ## Prerequisites
      
      - Read `package.json` to confirm the Next.js major version and whether the app uses the App Router or Pages Router. `experimental.serverActions` configuration keys, middleware conventions, and rewrite/redirect APIs differ across major versions and routing modes; do not apply App Router API names to a Pages Router codebase or vice versa.
      - Locate every candidate file in scope: `middleware.ts`/`middleware.js` at the project root, `next.config.js`/`next.config.mjs`, any `.env*` file, and any file containing a `'use server'` directive or exported Server Action.
      
      ## Workflow
      
      1. **Locate `middleware.ts`/`.js` and read its exported `config.matcher`.** Determine whether the `matcher` pattern excludes any path via a negative lookahead (e.g. `(?!api|_next)`). See `references/middleware-and-server-actions.md` for the decision tree.
      2. **For every path the matcher excludes, locate that path's own handlers** (Route Handlers, Server Actions) and confirm each one independently verifies the session/auth state inside its own function body — never assume middleware covers it.
      3. **Locate `next.config.js` and read any `experimental.serverActions` block.** If the deployment topology involves a reverse proxy or multi-zone setup (check deployment docs, `rewrites()`/proxy config, or ask if unclear), confirm `allowedOrigins` is present in that block and lists the actual production origins.
      4. **Grep every `.env*` file and every `process.env.NEXT_PUBLIC_*` usage** for a variable name suggesting a secret (`KEY`, `SECRET`, `TOKEN`, `PASSWORD`, `CREDENTIAL`, or equivalent). See `references/env-and-ssrf-surfaces.md`.
      5. **Grep `next.config.js` for `images.dangerouslyAllowLocalIP`.** If `true`, confirm dynamic `src` values passed to `<Image>` are validated against a hardcoded external-hostname allowlist somewhere on their data-flow path.
      6. **Grep for `NextResponse.rewrite(` and `rewrites()` destinations.** For each, trace the destination value's origin backward. If it is built from user-controlled input (query parameters, headers, request body) with no hostname allowlist check before the rewrite call, this is an SSRF/open-redirect finding.
      7. **Produce ranked findings** using the output contract below.
      
      ## Decision tree
      
      - Middleware `matcher` excludes a path via negative lookahead, and that path's own handler has no independent session check → **HIGH** finding, `middleware-auth-gap`. Cite the documented execution-order rule directly (`documentation-based`): a Proxy matcher that excludes a path also skips Server Function calls on that path.
      - Middleware `matcher` excludes a path, but that path's own handler (Server Action or Route Handler) demonstrably verifies the session itself → not a finding for that path; note it explicitly as reviewed-and-clear.
      - `serverActions` block exists, deployment is cross-origin (reverse proxy/multi-zone), and `allowedOrigins` is absent from that block → **HIGH** finding, `csrf-origin`.
      - `serverActions` block exists and includes `allowedOrigins` listing the actual production origins, or the deployment is same-origin only and the key is legitimately omitted → not a finding.
      - A `NEXT_PUBLIC_`-prefixed variable name matches a secret-shaped pattern (`KEY`/`SECRET`/`TOKEN`/`PASSWORD`/`CREDENTIAL`) → **HIGH** finding, `secret-leak`, regardless of whether the value has been rotated since — the bundle already shipped it to every prior client that loaded the page.
      - A `NEXT_PUBLIC_`-prefixed variable holds genuinely non-secret data (an analytics ID, a public feature flag) → not a finding.
      - `images.dangerouslyAllowLocalIP: true` with no demonstrable `src` allowlist validation → **HIGH** finding, `ssrf-redirect`.
      - `images.dangerouslyAllowLocalIP` is `false` or absent (the default) → not a finding.
      - `NextResponse.rewrite()`/rewrite destination built from user-controlled input with no hostname allowlist check before the call → **HIGH** finding, `ssrf-redirect`.
      - Rewrite destination is a fixed literal path, or a dynamic value is checked against a hardcoded allowlist before the rewrite call → not a finding.
      
      ## Output contract
      
      Every response from this skill must return:
      
      1. **Scope** — the middleware file(s), Server Action(s), `next.config.js`, and/or environment file(s) reviewed.
      2. **Ranked findings** — each with file:line, defect category (`middleware-auth-gap` / `csrf-origin` / `secret-leak` / `ssrf-redirect`), the concrete data-flow trace (matcher pattern and excluded-path handler status, serverActions config and deployment topology, environment variable declaration and usage site, or origin-to-sink path for SSRF/redirect), and a fix sketch matching Next.js's documented pattern.
      3. **Middleware-auth-gap status per excluded path** — an explicit statement of whether the excluded path's own handler independently verifies the session; never infer one does.
      4. **Evidence level per finding** — `repo evidence`, `documentation-based`, or `inference`. Label structural risk findings as structural risk explicitly — do not imply confirmed exploitation without live evidence (e.g., a captured cross-origin CSRF success, a confirmed SSRF callback).
      5. **Verdict** — approve / approve-with-notes / block.
      6. **Open questions or out-of-scope items** — e.g., "confirming actual cross-origin CSRF exploitation requires a live cross-origin request, not static review," or "component-level data-fetching patterns in this same file are out of scope — recommend a general Next.js review if needed."
      
      ## When to push back
      
      Push back if the user asks to:
      
      - approve a middleware `matcher` that excludes `/api` (or another path) as sufficient authorization coverage without checking whether that excluded path's own handlers verify the session — "middleware is there" is not evidence for a path middleware never runs on,
      - skip the `serverActions.allowedOrigins` check because "we haven't seen a CSRF incident" — this defect class is structural and often invisible until an attacker actually attempts a cross-origin form submission,
      - clear a `NEXT_PUBLIC_`-prefixed secret because "we're going to rotate it" — rotation does not undo exposure already shipped to every client that loaded a build containing the old value, and the review is about the structural leak, not the current key's live validity,
      - downgrade an untraced SSRF/rewrite finding to informational because "it's probably fine" — this skill's default is HIGH for exactly this class of unproven claim.
      
  • metadata.json 2 KB
    {
      "id": "nextjs-server-security-review",
      "name": "Next.js Server Security Review",
      "type": "skill",
      "provider": "frontend",
      "harnesses": [
        "claude-code",
        "cursor",
        "codex",
        "gemini",
        "kiro",
        "other"
      ],
      "summary": "Reviews Next.js middleware, Server Actions, next.config.js, and environment-variable files for middleware matcher exclusions that silently skip auth on Server Functions, Server Actions missing allowedOrigins CSRF protection, secrets leaked via NEXT_PUBLIC_ prefixes, and SSRF/open-redirect via dangerouslyAllowLocalIP or unvalidated rewrite destinations, grounding claims via Context7 and Next.js's own documentation.",
      "source_type": "original",
      "official_docs": [
        "https://nextjs.org/docs/app/building-your-application/authentication",
        "https://nextjs.org/docs/app/guides/data-security",
        "https://nextjs.org/docs/app/guides/environment-variables",
        "https://nextjs.org/docs/app/api-reference/file-conventions/proxy",
        "https://owasp.org/www-community/attacks/Server_Side_Request_Forgery",
        "https://owasp.org/www-community/attacks/xss/"
      ],
      "security_notes": "This skill's entire scope is security-critical: a middleware matcher exclusion is a zero-trust boundary defect (authorization silently skipped on excluded paths), a missing serverActions.allowedOrigins is a CSRF gap, a NEXT_PUBLIC_-prefixed secret is a build-time data exposure with no runtime remediation once shipped, and dangerouslyAllowLocalIP or an unvalidated rewrite destination is a server-side request forgery / open-redirect vector. Every finding in this skill defaults to HIGH severity unless proven otherwise with concrete repo evidence. Static-review-only skill: it reads and greps middleware, Server Action, config, and environment files but never executes, builds, or runs application code, and never sends live requests.",
      "last_verified": "2026-07-03",
      "path": "skills/frontend/nextjs-server-security-review",
      "author": "github: VincentChuWaiChow",
      "version": "0.1.0"
    }
    
  • SKILL.md 8.9 KB
    ---
    name: nextjs-server-security-review
    description: Statically review Next.js middleware, Server Actions, next.config.js, and environment-variable files for four documented server-side defect classes -- middleware matcher exclusions that silently skip auth on Server Functions, Server Actions missing allowedOrigins CSRF protection, secrets leaked via NEXT_PUBLIC_ prefixes, and SSRF/open-redirect via dangerouslyAllowLocalIP or unvalidated rewrite destinations.
    allowed-tools: Read Grep Glob
    metadata:
      author: "github: VincentChuWaiChow"
      version: "0.1.0"
      updated: "2026-07-03"
      category: security
    ---
    
    # Next.js Server Security Review
    
    ## Purpose
    
    Review Next.js middleware (`middleware.ts`/`.js`), Server Actions, `next.config.js`, and environment-variable files for the concrete server-side defect classes Next.js's own documentation calls out directly: middleware `matcher` exclusions that silently skip authorization for Server Functions, Server Actions missing `allowedOrigins` CSRF protection, secrets accidentally exposed to the client via a `NEXT_PUBLIC_` prefix, and server-side request forgery / open-redirect risk via `dangerouslyAllowLocalIP` or an unvalidated rewrite destination. This skill exists so the review stays anchored to these documented defect classes instead of drifting into a general "Next.js code review" of component architecture, data fetching patterns, or styling.
    
    ## When to use
    
    Use this skill when the user asks to:
    
    - review `middleware.ts`/`middleware.js` and its `matcher` configuration for authorization gaps,
    - assess whether a Server Action is protected against CSRF, or whether `next.config.js`'s `serverActions.allowedOrigins` is configured correctly for a reverse-proxy or multi-zone deployment,
    - audit `.env`/`.env.production` files or any `process.env.NEXT_PUBLIC_*` usage for accidental secret exposure to the client bundle,
    - review `next.config.js` image configuration (`images.dangerouslyAllowLocalIP`) or `rewrites()`/`NextResponse.rewrite()` usage for SSRF or open-redirect risk,
    - perform a pre-launch security review of a Next.js server surface (middleware, Server Actions, config, env files).
    
    Do not use this skill for:
    
    - client-only React component logic with no middleware, Server Action, config, or environment-variable surface in scope — there is no server-side sink for this skill to review,
    - general Next.js performance, data-fetching-pattern, or App Router/Pages Router migration review with no security angle,
    - a bug that requires live traffic reproduction (actually triggering a CSRF request cross-origin, actually confirming an SSRF callback from a deployed image optimizer) to prove exploitation — static analysis proves the structural risk, not that it has already been exploited in production.
    
    ## Context7 Documentation Protocol
    
    - Resolve the Next.js library ID with `resolve-library-id` (matched result: `/vercel/next.js`) before citing any middleware, Server Actions, environment-variable, or `next.config.js` behavior claim.
    - `/vercel/next.js` is a high-reputation source covering authentication, middleware, Server Actions, environment variables, and `next.config.js` security patterns directly from the framework's own docs and source. Use `query-docs` against it to confirm exact option names (`serverActions.allowedOrigins`, `images.dangerouslyAllowLocalIP`) and documented behavior (`NEXT_PUBLIC_` inlining, matcher exclusion semantics) before writing a finding.
    - Read `package.json` first to confirm the Next.js major version and whether the app uses the App Router or Pages Router — `experimental.serverActions` configuration, middleware `matcher` conventions, and `NextResponse.rewrite()` APIs have shifted across major versions; do not apply App Router API names to a Pages Router codebase or vice versa.
    - 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
    
    - All four defect classes in this skill's scope default to HIGH severity. This is a security-scoped skill: do not downgrade a middleware-matcher authorization gap, a missing CSRF allowlist, a leaked secret, or a structural SSRF/open-redirect path to MEDIUM just because it has not been observed exploited yet — the risk is in the structure, not in whether someone has already hit it.
    - Trace every finding to a concrete file:line and a concrete data-flow path. A finding that says "this middleware might not protect everything" or "this env var might leak" without showing the specific `matcher` pattern, the specific `serverActions` config, or the specific variable declaration is not a valid finding — it is a guess.
    - Never accept a middleware `matcher` as sufficient authorization coverage in isolation. A `matcher` whose negative-lookahead pattern excludes a path (e.g. `/((?!api|_next).*)`) means middleware — and any auth check it performs — never runs for that excluded path; a Proxy matcher that excludes a path also skips Server Function calls on that path. Confirm the excluded path's own handlers (Server Actions, Route Handlers) independently verify the session themselves.
    - Never approve a `next.config.js` with a `serverActions` block configured for a reverse-proxy, multi-zone, or otherwise cross-origin deployment unless `allowedOrigins` is explicitly present in that same block. Next.js's default CSRF protection compares the Server Action request's `Origin` header to the `Host` header and rejects mismatches — a deployment where those two headers legitimately differ (reverse proxy, multi-zone) needs the allowlist or every legitimate request fails, or worse, the mismatch is worked around insecurely elsewhere.
    - Flag every environment variable whose name suggests a secret (contains `KEY`, `SECRET`, `TOKEN`, `PASSWORD`, `CREDENTIAL`, or equivalent) and is prefixed `NEXT_PUBLIC_`. Any `NEXT_PUBLIC_` variable is inlined into the JavaScript bundle at build time and shipped to every client, full stop — there is no runtime gate that can retroactively hide it once bundled. The safe fix is to drop the prefix and read the value only from server-side code via `process.env.API_KEY` (or the equivalent unprefixed name), never from a Client Component.
    - Flag `images.dangerouslyAllowLocalIP: true` in `next.config.js` as a structural SSRF risk unless the codebase demonstrably validates every dynamic `src` value against a hardcoded external-hostname allowlist before it reaches the `<Image>` component.
    - Flag any `NextResponse.rewrite()` call (or `rewrites()` destination) whose target URL is built directly from user-controlled input (query parameters, headers, request body) with no hostname allowlist check on the resolved value before the rewrite call — this is SSRF/open-redirect via the rewrite backend, not a cosmetic routing bug.
    - 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).
    - Load only the reference needed for the concern in scope.
    
    ## 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 decision tree per defect class, and the required output shape.
    - [Middleware and Server Actions boundary defects](references/middleware-and-server-actions.md) — load only when reviewing `middleware.ts`/`.js`, its `matcher`, or Server Action CSRF configuration.
    - [Environment variables and SSRF/redirect surfaces](references/env-and-ssrf-surfaces.md) — load only when the review scope includes `.env*` files, `NEXT_PUBLIC_*` usage, `images.dangerouslyAllowLocalIP`, or a rewrite/redirect destination built from dynamic input. Includes the OWASP SSRF/open-redirect grounding reference; load that citation only when such a finding is actually present.
    
    ## Response minimum
    
    Return, at minimum:
    
    - the middleware file(s), Server Action(s), `next.config.js`, and/or environment file(s) in scope,
    - ranked findings with file:line evidence, defect category (`middleware-auth-gap`, `csrf-origin`, `secret-leak`, or `ssrf-redirect`), the concrete data-flow trace (the `matcher` pattern and the excluded path's own auth handling, the `serverActions` config and its deployment topology, the environment variable declaration and its usage site, or the origin-to-sink path for the SSRF/redirect finding), and a fix sketch matching Next.js's documented pattern,
    - for every middleware-auth-gap finding, an explicit statement of whether the excluded path's own handler independently verifies the session — never approve on the assumption it does,
    - evidence level per finding (`repo evidence`, `documentation-based`, or `inference`), with structural risk findings explicitly labeled as structural risk, not as confirmed-exploited,
    - verdict (approve / approve-with-notes / block),
    - open questions or scope the review could not cover (e.g., "confirming actual cross-origin CSRF success requires a live cross-origin request, not static review").
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related