Home/Learn/Next.js/Headers, CSP and Common Vulnerabilities

Headers, CSP and Common Vulnerabilities

Advanced
Auth & Security

A handful of response headers block whole categories of attack. A Content-Security-Policy is the strongest and the fiddliest, because Next injects inline scripts of its own.

Overview

Security headers are among the cheapest work in web development — set once, in one file, and several classes of attack stop being possible. The one that takes real effort is the Content-Security-Policy, because a strict policy forbids inline scripts and Next legitimately emits them for hydration data. The nonce mechanism resolves that, at the cost of making every response dynamic. This concept also covers the framework-specific vulnerabilities worth knowing by name, since interviewers ask and one of them was a genuine 2025 incident.

The Headers Worth Setting

Set once in next.config, then verify what actually shipped.

Five headers, one config block
// next.config.js
async headers() {
  return [{
    source: '/:path*',
    headers: [
      { key: 'Strict-Transport-Security',
        value: 'max-age=63072000; includeSubDomains; preload' },
      { key: 'X-Content-Type-Options', value: 'nosniff' },
      { key: 'X-Frame-Options', value: 'DENY' },          // clickjacking
      { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
      { key: 'Permissions-Policy',
        value: 'camera=(), microphone=(), geolocation=()' },
    ],
  }]
}

// What each one prevents:
//   HSTS         a downgrade to http, and cookie theft over it
//   nosniff      a user upload being executed as a script
//   frame DENY   your page embedded in an attacker's iframe
//   Referrer     your full URLs leaking to third parties
//   Permissions  a third-party script silently using the camera

// Verify what actually shipped rather than what you configured — a
// proxy or CDN can strip or override:
curl -sI https://aicancode.org | grep -i strict-transport
// or securityheaders.com for a graded report.

Content-Security-Policy

The nonce dance, and why it costs static rendering.

Nonce in middleware, and connect-src must list your API
// A strict CSP blocks inline scripts — including the ones Next emits
// for hydration. The supported answer is a per-request nonce, set in
// middleware:
export function middleware(req: NextRequest) {
  const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
  const csp = [
    `default-src 'self'`,
    `script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
    `style-src 'self' 'unsafe-inline'`,          // Tailwind needs this
    `img-src 'self' data: https://*.supabase.co`,
    `connect-src 'self' https://api.aicancode.org`,
    `frame-ancestors 'none'`,
  ].join('; ')

  const headers = new Headers(req.headers)
  headers.set('x-nonce', nonce)
  const res = NextResponse.next({ request: { headers } })
  res.headers.set('Content-Security-Policy', csp)
  return res
}
// Next reads x-nonce and applies it to its own script tags.

// The cost: a per-request nonce means the page cannot be statically
// cached. For a content site, that trade is often not worth it —
// a policy without 'unsafe-inline' on styles and with a tight
// connect-src already removes most of the risk.

// The self-inflicted failure to know about: omitting your own API
// from connect-src blocks your fetches and looks exactly like a CORS
// error in the console. Check connect-src before blaming the server.

// Roll it out with Content-Security-Policy-Report-Only first and read
// the violations for a week.

Framework-Specific Vulnerabilities

The ones with names, and the general rules underneath them.

Patch, allow-list redirects, never fetch a user URL
// CVE-2025-29927 — middleware bypass via a crafted
// x-middleware-subrequest header. Apps relying on middleware as their
// only auth gate were exposed. Patched; the lesson stands: authorise
// where data is accessed, not in middleware.

// Cache poisoning via headers — a poisoned response served to other
// users. Keep Next patched; treat the CDN cache key as security-
// relevant, and never cache a per-user response.

// The evergreen ones, which are yours rather than the framework's:
//   XSS               dangerouslySetInnerHTML with unsanitised input
//                     -> DOMPurify, or do not render user HTML at all
//   open redirect     redirect(searchParams.next) with no validation
//                     -> allow-list the destination, or require a
//                        relative path starting with a single /
//   SSRF              fetch(userSuppliedUrl) from a server component
//                     -> allow-list hosts; the server can reach your
//                        internal network and the browser cannot
//   IDOR              a query without an ownership filter
//   secret leakage    a server module imported by a client component

// Keep dependencies current — this is where most real incidents come
// from, not from clever attacks:
npm audit
// and enable Dependabot or Renovate.

Key Points to Remember

  • 1HSTS, nosniff, frame-ancestors, Referrer-Policy and Permissions-Policy are one config block that closes several attack classes
  • 2A strict CSP needs a per-request nonce from middleware, which forfeits static caching — a real trade-off for content sites
  • 3Omitting your own API from connect-src blocks your fetches and looks exactly like a CORS error
  • 4CVE-2025-29927 let middleware be bypassed with a header — authorise where data is accessed, never only in middleware
  • 5Validate redirect destinations against an allow-list and never fetch a user-supplied URL from the server

Interview Questions

Sign in to ask Aria
1

Why does a strict CSP conflict with static rendering in Next.js?

Hard
2

What is an open redirect and how do you prevent one?

Medium
3

Why is SSRF a server-side concern that does not exist in the browser?

Hard

Ask Aria about Headers, CSP and Common Vulnerabilities

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…