Chapter 31 · Security Best Practices
Subchapter 31.5
references/javascript-typescript-nextjs-web-server-security.mdMarkdown42 KBView on GitHub
This document is designed as a security spec that supports:
Also bundled
OpenAIIt is intentionally written as a set of normative requirements (“MUST/SHOULD/MAY”) plus audit rules (what bad patterns look like, how to detect them, and how to fix/mitigate them).
Target scope: Next.js 16.1.x (latest line shown in the App Router docs) (Next.js (opens in a new tab)), running on Node.js 20.9+ (per Next.js system requirements). (Next.js (opens in a new tab))
process.env dumps, database URLs with credentials).*, skipping authz checks, turning off cookie security flags, turning off CSP because it’s “hard”).When asked to write new Next.js code or modify existing code:
While working anywhere in a Next.js repo (even if the user did not ask for a security scan):
When the user asks to “scan”, “audit”, or “hunt for vulns”:
Recommended audit order:
package.json scripts, hosting config).next.config.*), Proxy/Middleware, routing patterns.In Next.js backends, untrusted input includes:
App Router:
Route Handler params and request data:
context.params (dynamic segments), search params (request.url, new URL(request.url).searchParams)request.headers, request.cookiesawait request.json(), await request.formData(), await request.text()Dynamic APIs used in Server Components/Server Functions:
headers() and cookies() values (Next.js (opens in a new tab))Pages Router:
req.query, req.cookies, req.body in pages/api/* handlers (Next.js (opens in a new tab))Plus:
A request is state-changing if it can create/update/delete data, change auth/session state, trigger side effects (purchase, email send, webhook send), or initiate privileged actions.
Special note for Next.js:
For each issue found, output:
This is the smallest “production baseline” that prevents common Next.js backend misconfigurations.
next build + next start (or the managed platform equivalent), not next dev. Dev mode has different error/reporting behavior and is not designed for production exposure. (Next.js (opens in a new tab))NODE_ENV=production in production (Next.js defaults NODE_ENV based on command; verify the runtime environment). (Next.js (opens in a new tab))X-Content-Type-Options, clickjacking defense via CSP frame-ancestors and/or X-Frame-Options, etc.). Next.js provides guidance for implementing CSP via Proxy/headers. (Next.js (opens in a new tab))Secure, HttpOnly, SameSite) as appropriate. (Next.js (opens in a new tab))
IMPORTANT NOTE: Only set Secure in production environment. When running in a local dev environment over HTTP, do not set Secure property on cookies. You should do this conditionally based on if the app is running in production mode. You should also include a property like SESSION_COOKIE_SECURE which can be used to disable Secure cookies when testing over HTTP.NEXT_PUBLIC_* environment variables as public (browser-exposed and inlined at build time). (Next.js (opens in a new tab))Each rule contains: required practice, insecure patterns, detection hints, and remediation.
Severity: High (if production)
NOTE: If they are deploying to a specific Next.js hosting provider, they do not need to worry about this.
Required:
next dev or any development server mode to production.Insecure patterns:
next dev in Docker CMD, Procfile, platform start command.NODE_ENV=development in production environment config.Detection hints:
package.json scripts and deployment manifests for next dev.NODE_ENV=development or missing NODE_ENV.next dev.Fix:
next build during CI/build and next start at runtime (or platform-native build/run).NODE_ENV=production.Note:
Severity: High (Critical if known-vulnerable version)
Required:
Insecure patterns:
next to a vulnerable range.Detection hints:
package.json and lockfiles for next version.IMPORTANT: Any versions older than these minor versions are vulnerable to “react2shell” vulnerability (https://nextjs.org/blog/CVE-2025-66478 (opens in a new tab)): 15.0.5 15.1.9 15.2.6 15.3.6 15.4.8 15.5.7 16.0.7
Fix:
next to a supported and patched version.Severity: High (Critical if secret is client-exposed)
Required:
.env* files..env* as sensitive; Next.js warns you “almost never want to commit these files.” (Next.js (opens in a new tab))NEXT_PUBLIC_* environment variable as public and browser-visible (inlined into the client bundle at build time). (Next.js (opens in a new tab))Insecure patterns:
.env, .env.local, .env.production committed to git.NEXT_PUBLIC_API_KEY, NEXT_PUBLIC_SECRET, NEXT_PUBLIC_DATABASE_URL, etc.process.env values into HTML or returning them from API routes.Detection hints:
.env content, DB_PASS=, API_KEY=, SECRET=.NEXT_PUBLIC_ and review any sensitive-looking names.process.env usage in Client Components ("use client") and shared modules.Fix:
NEXT_PUBLIC_ prefix)..env* is ignored and secrets are injected at deploy time.Severity: High
Required:
Insecure patterns:
"use client" components.lib/ modules imported by both server and client code that reference secrets.Detection hints:
"use client" and examine its imports for server-only dependencies.pg, mysql2, mongoose, prisma, admin SDKs) imported from components/ or other client paths.process.env access in UI components.Fix:
lib/server/* and only import from server contexts (Route Handlers, Server Components, Server Actions).Severity: High
Required:
MUST enforce authn/authz in server-side code for:
app/**/route.ts) (Next.js (opens in a new tab))pages/api/**) (Next.js (opens in a new tab))"use server" functions invoked by clients) (Next.js (opens in a new tab))MUST NOT rely on client-side checks (hiding UI, route guards on the client) as the only protection.
Insecure patterns:
Detection hints:
"use server" and review all exported actions for auth checks.Fix:
Severity: High
Required:
matcher, and for auth it’s recommended Proxy runs on all routes. (Next.js (opens in a new tab))matcher mistakes as an auth bypass risk.Insecure patterns:
/api/*, or only matches some route groups.Detection hints:
proxy.ts / middleware.ts and its matcher.app/api/** and pages/api/**).Fix:
Notes:
Severity: High
Required:
allowedOrigins with a strict allowlist. (Next.js (opens in a new tab))Insecure patterns:
allowedOrigins: ['*'] (or broad wildcards) or “reflect Origin” logic.Detection hints:
allowedOrigins and confirm the list is small, specific, and justified. (Next.js (opens in a new tab))Fix:
SameSite=Lax or Strict when compatible; don’t treat SameSite alone as sufficient.Notes:
Severity: Medium
Required (production, HTTPS):
MUST set session/auth cookies with:
Secure: true (HTTPS-only) IMPORTANT NOTE: Only set Secure in production environment. When running in a local dev environment over HTTP, do not set Secure property on cookies. You should do this conditionally based on if the app is running in production mode. You should also include a property like SESSION_COOKIE_SECURE which can be used to disable Secure cookies when testing over HTTP.HttpOnly: true (not readable by JS)SameSite: 'Lax' (recommended) or 'Strict' if compatibleOnly use SameSite: 'none' when you truly need cross-site cookies, and then MUST also set Secure. Cookie options are supported in Next.js cookie APIs. (Next.js (opens in a new tab))
Insecure patterns:
secure: false in production.httpOnly: false for auth cookies.sameSite: 'none' without a clear need, especially on cookie-authenticated state-changing endpoints.Detection hints:
cookies().set(...), Set-Cookie headers, auth library cookie config).Fix:
domain unless you explicitly need subdomain-wide cookies.Severity: Low
Required:
Insecure patterns:
Detection hints:
localStorage.setItem('token'...) and non-HttpOnly cookie usage.Fix:
Severity: High
Required:
req.body is any and must be validated before use. (Next.js (opens in a new tab))Insecure patterns:
req.body shape directly.params.id/searchParams directly into DB queries or file paths.Detection hints:
req.body. usage and for await request.json() usage in Route Handlers; verify validation exists.Fix:
Severity: Low
Required (typical web app):
SHOULD set:
Content-Security-Policy) (see NEXT-CSP-001)X-Content-Type-Options: nosniffframe-ancestors in CSP and/or X-Frame-Options)Referrer-Policy and Permissions-Policy when appropriateMUST ensure cookies are set with secure attributes (see NEXT-SESS-001). (Next.js (opens in a new tab))
Insecure patterns:
Content-Type sniffing possible due to missing nosniff.Detection hints:
proxy.ts / middleware for response.headers.set(...). (Next.js (opens in a new tab))Fix:
Severity: Medium
NOTE: It is most important to set the CSP’s script-src. All other directives are not as important and can generally be excluded for the ease of development.
Required:
script-src 'unsafe-inline') without explicit risk acceptance.Insecure patterns:
Detection hints:
Content-Security-Policy header setting and examine its directives.next/script and whether a nonce is provided when CSP requires it.Fix:
eval.Notes:
Severity: High
Required:
MUST rely on React’s default escaping; MUST NOT insert untrusted HTML into the DOM without sanitization.
MUST treat these as high-risk sinks:
dangerouslySetInnerHTML<script> tags or event handler attributesMUST avoid serving uploaded HTML as active HTML (serve as attachment or sanitize/transform).
Insecure patterns:
<div dangerouslySetInnerHTML={{ __html: userContent }} /> with no sanitizer.Content-Type: text/html from a Route Handler.Detection hints:
dangerouslySetInnerHTML, __html:.Fix:
Severity: High (Critical for privileged actions)
Required:
MUST apply the same controls as for Route Handlers:
MUST NOT assume Server Actions are “not reachable” or “internal”.
MUST understand Server Action request protections:
allowedOrigins. (Next.js (opens in a new tab))Insecure patterns:
"use server" functions that update DB state with no auth check.allowedOrigins to “make it work”.Detection hints:
"use server" and inventory all exported actions.Fix:
allowedOrigins minimal and audited.Severity: Medium (High if important secrets are exposed)
Required:
.bind are not encrypted; do not rely on .bind to protect secrets. (Next.js (opens in a new tab))Insecure patterns:
myAction.bind(null, process.env.SECRET) or binding sensitive tokens/IDs that should not be client-influenced.Detection hints:
.bind( on Server Action functions.process.env usage near Server Actions.Fix:
Severity: High (Critical if cross-user data leak)
Required:
use cache and similar caching mechanisms as potentially cross-user unless explicitly proven private; do not cache per-user DB results in shared caches. (Next.js (opens in a new tab))Cache-Control: no-store / private for sensitive responses (auth/session/user data APIs).Insecure patterns:
export const dynamic = 'force-static' on a route that returns user-specific data. (Next.js (opens in a new tab))use cache around a function that queries user-specific data without a per-user cache key. (Next.js (opens in a new tab))Detection hints:
dynamic = 'force-static', revalidate, use cache, cacheLife, unstable_cache.cookies()/headers() (dynamic APIs) is not accidentally removed in ways that make a route static. (Next.js (opens in a new tab))Fix:
Cache-Control: no-store.Severity: Medium
Required:
public/ directory (anything under public/ is served as static content by default).Content-Disposition: attachment) unless explicitly intended.Insecure patterns:
public/uploads/ and serving them directly.Detection hints:
formData() / multipart parsing, fs.writeFile, storage SDK usage.public/.Content-Type: text/html or serve user files inline.Fix:
Severity: High
Required:
Insecure patterns:
fs.readFile(request.nextUrl.searchParams.get('path'))path.join(base, userPath) without normalization + boundary checksDetection hints:
fs. usage in Route Handlers/API Routes.path.join/path.resolve fed by request params.Fix:
.. from being used when creating urlsSeverity: Medium (High in internal networks)
NOTE: This is mostly only applicable to apps which will be deployed in a cloud/LAN setup or have other http services on the same box. Sometimes the feature requires this functionality unavoidably (webhooks).
Required:
MUST treat any server-side fetch() to a user-provided URL as high-risk.
SHOULD allowlist destinations (hosts/domains) for URL fetch features.
SHOULD block:
MUST restrict protocols to http: and https:.
SHOULD set strict timeouts and restrict redirects.
Insecure patterns:
await fetch(req.query.url) or await fetch((await request.json()).url)Detection hints:
fetch( in server code and trace where the URL comes from.Fix:
http/https, allowlist hostnames, re-resolve DNS/IP to block private ranges.Severity: Low
Required:
next, redirect, returnTo).http or https: schema, disallowing javascript: schemaInsecure patterns:
redirect(searchParams.get('next')!)NextResponse.redirect(new URL(req.nextUrl.searchParams.get('to')!, req.url)) without checksDetection hints:
redirect( (server components/actions) and NextResponse.redirect.res.redirect( in API Routes. (Next.js (opens in a new tab))Fix:
/path) and reject protocol-relative (//evil.com) or absolute URLs.Severity: Medium (High if misconfigured with credentials)
Required:
If CORS is not needed, MUST keep it disabled.
Next.js API Routes do not set CORS headers by default, meaning they are same-origin by default; only enable CORS when you truly need it. (Next.js (opens in a new tab))
If enabling CORS:
Insecure patterns:
Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: trueOrigin without validation.Detection hints:
Access-Control-Allow-Origin, cors, “CORS” middleware/wrappers.OPTIONS handlers.Fix:
Severity: Medium
Required:
Insecure patterns:
JSON.stringify(req.body) (can change formatting).Detection hints:
/api/webhook, /app/api/**/webhook).Fix:
Severity: High
Required:
Insecure patterns:
db.query(`SELECT * FROM users WHERE id = ${id}`)"WHERE name = '" + user + "'"Detection hints:
SELECT, INSERT, UPDATE, DELETE strings.params, searchParams, req.query, req.body, request.json()) into DB calls.Fix:
Severity: Critical to High
Required:
MUST avoid executing OS commands with attacker-controlled input.
If subprocess is necessary:
shell: true with attacker-influenced stringsInsecure patterns:
exec("convert " + filename)spawn("bash", ["-c", userInput])spawn(userInput, ["foo"])Detection hints:
child_process, exec, spawn, shell: true.Fix:
-- to separate flags where supported).Severity: High to Critical
Required:
eval, new Function, vm.runIn* on untrusted strings.Insecure patterns:
eval(req.body.code)Detection hints:
eval(, new Function, vm., require( with non-literals.js-yaml, XML parsers, custom serializer usage on untrusted input.Fix:
Severity: Medium
Required:
MUST NOT log:
Authorization headersSHOULD implement structured logging with redaction.
Insecure patterns:
console.log(req.headers) in auth endpointsconsole.log(process.env) in server codeDetection hints:
console.log(, logger.info(, debug( in server routes/actions.Fix:
Severity: Low
Required:
Insecure patterns:
err.stack in JSON responses.Detection hints:
res.status(500).json(err) or return Response.json(err).Fix:
Severity: Medium
Required:
MUST be careful when copying/forwarding request headers upstream:
x-forwarded-* headers unless you have a trusted proxy chain.Authorization/cookies to unrelated outbound services.Next.js Proxy patterns often mutate headers; ensure this doesn’t create security issues.
Insecure patterns:
fetch() call.x-forwarded-host or host to construct sensitive absolute URLs without allowlisting.Detection hints:
headers() and request.headers usage (especially for URL building). (Next.js (opens in a new tab))Fix:
Severity: Medium
Required:
Host headers.Insecure patterns:
const base = "https://" + request.headers.get("host")x-forwarded-host for absolute URL generation.Detection hints:
.get('host'), .get('x-forwarded-host'), and absolute URL building.Fix:
APP_ORIGIN=https://example.com).Severity: Medium
Required:
SHOULD implement rate limiting/throttling for:
MUST implement request size limits (see NEXT-LIMITS-001).
If self-hosting, MUST rely on reverse proxy for additional protections. (Next.js (opens in a new tab))
Insecure patterns:
Detection hints:
Fix:
When actively scanning, use these high-signal patterns:
Production misconfig:
next dev, NODE_ENV=development, dev-only start commands (Next.js (opens in a new tab))Secrets exposure:
.env committed, NEXT_PUBLIC_ on sensitive variables (Next.js (opens in a new tab))process.env used in "use client" modulesAuth coverage:
app/**/route.ts or pages/api/** with no auth checks (Next.js (opens in a new tab))"use server" actions with DB writes and no authz (Next.js (opens in a new tab))proxy.ts / middleware.ts matchers that exclude sensitive routes (Next.js (opens in a new tab))CSRF:
serverActions.allowedOrigins too broad (Next.js (opens in a new tab))XSS:
dangerouslySetInnerHTML, raw HTML markdown renderingCaching/data leak:
dynamic = 'force-static' on sensitive GET handlers (Next.js (opens in a new tab))use cache, cacheLife, unstable_cache around user-specific data (Next.js (opens in a new tab))Files:
public/fs.readFile / path.join with request inputSSRF:
fetch(userProvidedUrl) from Route Handlers / Server ActionsRedirect:
redirect(searchParams.get('next')), NextResponse.redirect(...), res.redirect(req.query.next) (Next.js (opens in a new tab))CORS:
Limits:
bodyParser: false and no raw-body verification for webhooks (Next.js (opens in a new tab))serverActions.bodySizeLimit raised without justification (Next.js (opens in a new tab))Dependency hygiene:
next versions that conflict with support policy/advisories (Next.js (opens in a new tab))Always try to confirm:
Primary framework documentation (Next.js):
https://nextjs.org/docs/app/getting-started/installationhttps://nextjs.org/docs/app/getting-started/route-handlershttps://nextjs.org/docs/pages/building-your-application/routing/api-routeshttps://nextjs.org/docs/pages/guides/environment-variableshttps://nextjs.org/docs/app/guides/data-securityhttps://nextjs.org/docs/app/guides/content-security-policyhttps://nextjs.org/docs/app/getting-started/proxyserverActions.allowedOrigins and serverActions.bodySizeLimit — https://nextjs.org/docs/app/api-reference/config/next-config-js/serverActionscookies() — https://nextjs.org/docs/app/api-reference/functions/cookiesheaders() — https://nextjs.org/docs/app/api-reference/functions/headershttps://nextjs.org/docs/pages/guides/self-hostinghttps://nextjs.org/docs/support-policyNext.js security guidance & advisories:
https://nextjs.org/blog/security-nextjs-server-components-actionshttps://github.com/advisories/GHSA-fq29-rrrv-cq2mhttps://nextjs.org/blog/security-updateGeneral web security references (recommended baseline):
https://cheatsheetseries.owasp.org/This file