# Nextjs Security

> Security audit specific to Next.js applications including App Router and Pages Router, Server Actions, middleware, Route Handlers, Server Components, environment variable exposure (NEXT_PUBLIC_), getServerSideProps/getStaticProps secret leakage, Image Optimization SSRF, and Next.js-specific authentication patterns. Use this skill whenever the user mentions Next.js, App Router, Pages Router, Server Actions, RSC, server components, middleware.ts, route handlers, NEXT_PUBLIC_, Vercel deployment patterns, next.config.js, or asks "audit my Next.js app", "is my middleware safe", "Server Actions security", "Next.js auth review". Trigger when the codebase contains a `next.config.js`/`next.config.mjs`/`next.config.ts` file, `app/` directory with `page.tsx` files, or `pages/` directory with Next.js conventions.

- Skill: `hlsitechio/nextjs-security` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add hlsitechio/nextjs-security`
- Raw SKILL.md: https://api.skillmd.com/api/skills/hlsitechio/nextjs-security/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: hlsitechio (https://skillmd.com/u/hlsitechio)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/hlsitechio/nextjs-security

---


# Next.js Security Audit

Audit a Next.js application for framework-specific vulnerabilities. Covers App Router (13+) and Pages Router. Defensive focus.

## When this skill applies

- Reviewing Next.js apps for security issues
- Auditing Server Actions, Route Handlers, middleware
- Reviewing environment variable usage (the `NEXT_PUBLIC_` trap)
- Checking authentication patterns specific to Next.js
- Identifying SSRF in `next/image` configurations
- Auditing the differences between Server and Client Components

Use other skills for: generic React patterns (`react-security`), backend services Next calls (`nodejs-express-security` etc.), auth providers (`clerk-security`, `nextauth-security`), Vercel platform settings (`vercel-platform-security`).

## Workflow

Follow `../_shared/audit-workflow.md`. Next.js-specific notes below.

### Phase 1: Stack detection

- Next.js version (13, 14, 15 — App Router behaviors differ)
- Router model: App Router (`app/`), Pages Router (`pages/`), or hybrid
- Deployment target: Vercel, self-hosted Node, edge runtime, static export
- Auth: NextAuth/Auth.js, Clerk, custom JWT, Supabase Auth, other

### Phase 2: Inventory

```bash
# Layout
find . -name 'next.config.*' -not -path '*/node_modules/*'
find app -type f \( -name 'page.tsx' -o -name 'route.ts' -o -name 'layout.tsx' \) 2>/dev/null | head -20
find pages -type f \( -name '*.tsx' -o -name '*.ts' \) 2>/dev/null | head -20

# Server Actions (App Router)
grep -rn "'use server'" app/ src/ 2>/dev/null | head -50

# Middleware
find . -name 'middleware.ts' -o -name 'middleware.tsx' -o -name 'middleware.js' 2>/dev/null | head

# Environment variable references
grep -rn "process.env" app/ src/ pages/ 2>/dev/null | head -50

# next/image config (image optimization)
grep -n 'images:\|remotePatterns\|domains:' next.config.* 2>/dev/null
```

### Phase 3: Detection — the checks

#### Environment variables — the `NEXT_PUBLIC_` trap

- **NXT-ENV-1** Variables prefixed with `NEXT_PUBLIC_` are inlined into the client bundle at build time. Anything not strictly public must NOT have this prefix.
- **NXT-ENV-2** Common mistakes: `NEXT_PUBLIC_STRIPE_SECRET_KEY`, `NEXT_PUBLIC_DATABASE_URL`, `NEXT_PUBLIC_API_KEY` — all of these have shipped to production at multiple companies. Audit every `NEXT_PUBLIC_` for "is this really public?"
- **NXT-ENV-3** Verify by building and inspecting:
  ```bash
  npm run build && \
    grep -rhE 'NEXT_PUBLIC_[A-Z_]+' .next/static/chunks/*.js | sort -u
  # If any NEXT_PUBLIC_*_SECRET or NEXT_PUBLIC_*_KEY values show up, that's a leak
  ```
- **NXT-ENV-4** Server-only secrets read from `process.env.SOMETHING_SECRET` only in server components, server actions, route handlers, or `getServerSideProps` / `getStaticProps`. Never reference them in a Client Component or shared utility imported by both.

#### Server vs Client Components (App Router)

- **NXT-RSC-1** Files with `"use client"` are Client Components and ship to the browser; nothing in their import graph can contain secrets.
- **NXT-RSC-2** Files without `"use client"` in `app/` are Server Components — they can read secrets but must NOT pass them as props to Client Components (props serialize to the client).
- **NXT-RSC-3** Use the `server-only` package to fail-fast on accidental client imports of server modules:
  ```ts
  // lib/db.ts
  import 'server-only';
  export const db = createDbClient(process.env.DATABASE_URL!);
  ```
  If any Client Component transitively imports this, the build fails.
- **NXT-RSC-4** Use `client-only` similarly for browser-only modules.

#### Server Actions

Server Actions (`"use server"`) are POST endpoints generated by Next, callable from forms or directly from Client Components. They're a common new XSS-equivalent attack surface.

- **NXT-SA-1** Every Server Action checks authentication. There's no implicit auth — Next doesn't add one.
  ```ts
  'use server';
  import { auth } from '@/lib/auth';
  
  export async function deletePost(postId: string) {
    const session = await auth();
    if (!session?.user) throw new Error('Unauthorized');
    // ... and check the user owns the post
  }
  ```
- **NXT-SA-2** Server Actions check authorization on the specific resource — not just "is user authenticated" but "does user own/have-access-to this object".
- **NXT-SA-3** Input validated with Zod or similar; never trust the shape of the FormData / serialized args.
- **NXT-SA-4** Server Actions don't accept `userId` / `tenantId` as args — derive from session.
- **NXT-SA-5** Mutation Server Actions handle CSRF correctly. Next has built-in protection for Server Actions called via form submission with the same-origin policy, but custom invocations need verification.
- **NXT-SA-6** Server Actions that perform sensitive operations have rate limiting (no built-in; use a Redis/Upstash counter).

#### Route Handlers (App Router) and API Routes (Pages Router)

- **NXT-RH-1** Every Route Handler in `app/.../route.ts` and every API route in `pages/api/` checks auth.
- **NXT-RH-2** No accidental data exposure — handlers return only what the caller is authorized to see.
- **NXT-RH-3** CORS configured per handler when needed; not globally permissive.
- **NXT-RH-4** Webhook receivers (Stripe, Resend, etc.) verify signatures — see `saas-security-pack/saas-api-security/references/webhook-security.md`.
- **NXT-RH-5** Catch-all routes (`[...slug]/route.ts`) validate the captured path; common path traversal vector.

#### Middleware

`middleware.ts` runs before every matched request — a powerful place to enforce, also a powerful place to introduce bugs.

- **NXT-MW-1** Middleware that performs auth checks: ensure it's actually applied to the routes you think (the `config.matcher` field).
- **NXT-MW-2** Middleware running on the Edge runtime can't use Node.js APIs; if you need `crypto` for HMAC, use the Web Crypto API.
- **NXT-MW-3** Middleware doesn't leak: don't put sensitive headers, cookies, or info-disclosure into rewrites/responses from middleware.
- **NXT-MW-4** Middleware should not be the only auth check — it's easy to misconfigure matcher and route around. Server Actions / Route Handlers still check.
- **NXT-MW-5** `request.headers.get('x-forwarded-for')` and similar are spoofable client-side except behind a properly-configured reverse proxy. Don't use for security decisions without verifying the proxy chain.

#### Image Optimization SSRF

`next/image` can fetch from any URL listed in `images.remotePatterns` / `images.domains` in `next.config.js` and proxy through your server. Bad configs become SSRF.

- **NXT-IMG-1** `images.remotePatterns` lists specific hostnames; no wildcards like `**`.
- **NXT-IMG-2** No legacy `images.domains: ['*']` config.
- **NXT-IMG-3** Self-hosted Next.js: image optimizer can fetch internal IPs unless restricted. The Vercel-hosted version blocks RFC1918; self-hosted defaults more permissive.
- **NXT-IMG-4** If users can supply image URLs (avatars, embeds), the URLs go through `next/image` which proxies through your server — making your server the source IP for the fetch. Validate URLs before passing to next/image.

```js
// BAD
module.exports = {
  images: {
    remotePatterns: [{ protocol: 'https', hostname: '**' }],  // any host
  },
};

// GOOD
module.exports = {
  images: {
    remotePatterns: [
      { protocol: 'https', hostname: 'images.yourcdn.com' },
      { protocol: 'https', hostname: '*.s3.amazonaws.com', pathname: '/yourbucket/**' },
    ],
  },
};
```

#### Session and auth handling

- **NXT-AUTH-1** Session cookies follow the patterns in `saas-security-pack/saas-frontend-hardening/references/cookie-config.md`.
- **NXT-AUTH-2** If using NextAuth: see `nextauth-security` skill.
- **NXT-AUTH-3** If using Clerk: see `clerk-security` skill.
- **NXT-AUTH-4** Custom JWT: validate signature on every request; rotate JWT secret periodically; see `saas-security-pack/saas-code-security-review/references/jwt-validation.md`.
- **NXT-AUTH-5** Login redirect URLs validated against allowlist (open redirect via `callbackUrl=...`).

#### Static export vs runtime

- **NXT-EXP-1** Static-exported pages don't run server logic at request time; auth checks intended to run server-side don't. Confirm the deployment mode matches the security model.

#### `next.config.js` security flags

- **NXT-CFG-1** `headers()` configured to add CSP, HSTS, X-Content-Type-Options, etc. (see `saas-security-pack/saas-frontend-hardening`).
- **NXT-CFG-2** `poweredByHeader: false` (removes `X-Powered-By: Next.js` — info disclosure).
- **NXT-CFG-3** `reactStrictMode: true` — not strictly security, but surfaces hydration mismatches that can have security implications.
- **NXT-CFG-4** Custom redirect / rewrite rules don't accept user-controllable targets without validation (open redirect).

#### Dependency hygiene

- **NXT-DEP-1** Next.js version is a recently-patched release. Several Next.js versions have had Critical CVEs:
  - CVE-2024-46982 (cache poisoning on Pages Router)
  - CVE-2024-34351 (SSRF via Server Actions)
  - CVE-2025-29927 (middleware bypass — fixed in 14.2.25 / 15.2.3)
  - older CVEs in 13.x
  Pin to the latest patch of your supported major; review release notes for security fixes.

### Phase 4: Triage

Critical class examples:
- `NEXT_PUBLIC_*_SECRET` env vars (definitive client leak)
- Server Action without auth check performing sensitive ops
- Middleware-only auth bypassable via specific URL pattern (see CVE-2025-29927 class)
- next/image with wildcard remote patterns
- Server Component passing secret-bearing prop to Client Component

### Phase 5: Report

Use `../_shared/findings-schema.md`. Prefix IDs with `NXT-`.

## References

- `references/app-router-patterns.md` — Server Actions, RSC, middleware patterns and pitfalls

