SecureStartKit
SecurityFeaturesPricingDocsBlogChangelog
Sign inBuy Now
Home/Security/Missing Security Headers and CSP
CWE-693A02:2025 Security MisconfigurationMedium severityNext.js

Missing Security Headers and CSP

◐The kit ships a solid baseline (HSTS, X-Frame-Options, nosniff, Referrer-Policy) on every route, but no Content-Security-Policy and no Permissions-Policy.

Last reviewed June 13, 2026 by SecureStartKit Team

The short answer

Security headers are a cheap, app-wide layer of defense set once in next.config.ts. The baseline almost every app should send: Strict-Transport-Security, X-Frame-Options (or frame-ancestors), X-Content-Type-Options nosniff, and a sensible Referrer-Policy. The two that take real work and matter most against XSS and feature abuse are Content-Security-Policy and Permissions-Policy. Headers are defense in depth, not a substitute for escaping output and validating input.

Where it shows up: The app sends no Content-Security-Policy and no Permissions-Policy, or ships with none of the baseline headers configured in next.config.ts.

The vulnerable patterns and their fixes

No security headers at all

✗Vulnerablets
// next.config.ts  (no headers configured)
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  images: { remotePatterns: [{ protocol: 'https', hostname: '*.supabase.co' }] },
  // no async headers(): every response ships with browser defaults only
}

export default nextConfig

With no headers() function, responses carry no HSTS, no frame protection, and no nosniff. Every one of those is a one-line default the app is choosing to skip.

↓the fix
✓Securets
// next.config.ts  (baseline headers on every route)
const securityHeaders = [
  { key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
  { key: 'X-Frame-Options', value: 'DENY' },
  { key: 'X-Content-Type-Options', value: 'nosniff' },
  { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
]

const nextConfig = {
  async headers() {
    return [{ source: '/(.*)', headers: securityHeaders }]
  },
}

This is the kit’s actual baseline in next.config.ts. HSTS forces HTTPS, X-Frame-Options: DENY blocks framing, and nosniff stops MIME confusion, applied to every route.

Baseline set, but no CSP or Permissions-Policy

✗Vulnerablets
// baseline only: the XSS-containing header and feature lockdown are missing
const securityHeaders = [
  { key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
  { key: 'X-Frame-Options', value: 'DENY' },
  { key: 'X-Content-Type-Options', value: 'nosniff' },
  { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
  // no Content-Security-Policy
  // no Permissions-Policy
]

This is the kit today: a solid baseline, but nothing to contain an injected script and nothing to lock down browser features. The two highest-value headers are the two that are missing.

↓the fix
✓Securets
const securityHeaders = [
  { key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
  { key: 'X-Frame-Options', value: 'DENY' },
  { key: 'X-Content-Type-Options', value: 'nosniff' },
  { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
  {
    key: 'Content-Security-Policy',
    value: "default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'",
  },
  { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
]

The CSP restricts where scripts may load from and disallows inline script, so an injected payload cannot run or phone home. Permissions-Policy denies powerful browser features by default. Roll the CSP out in Content-Security-Policy-Report-Only first, watch the violation reports, then enforce.

SecureStartKit ships these defenses by default. RLS, Zod-validated Server Actions, and verified webhooks, already wired in.

Get SecureStartKit→

How it’s exploited

Each missing header removes a specific, cheap mitigation, and the costs compound.

No Content-Security-Policy is the expensive one. An injected script, whether from an XSS hole, a compromised third-party script, or an inline event handler, runs with full page privileges and can read tokens and exfiltrate them to any origin. A CSP that disallows inline script and restricts script-src to your own origin would have blocked the injection from running or stopped its outbound call. Without it, an XSS bug escalates straight to data theft with nothing in the way.

No X-Frame-Options or frame-ancestors means your authenticated pages can be loaded in a hidden iframe on an attacker site and used for clickjacking. No X-Content-Type-Options nosniff lets a browser MIME-sniff a response into a script context it was never meant to run in. No Strict-Transport-Security leaves the first request open to an HTTP downgrade or interception before HTTPS is enforced.

The realistic chain is the CSP one: a single reflected or stored XSS, which a content security policy would have contained, becomes full account takeover because the injected script faces no restriction on what it can run or where it can send what it reads.

How to find it in your code

Check what your deployed app actually sends. A single request shows every response header:

curl -sI https://your-app.com | grep -iE "content-security|x-frame|x-content-type|strict-transport|referrer|permissions-policy"

Anything absent from that list is a header you are not sending. Then open next.config.ts and confirm an async headers() function returns the set you expect for the /(.*) source.

For a scored report that flags missing and weak headers, run the app through a header scanner such as the Mozilla Observatory or securityheaders.com, and treat a missing Content-Security-Policy as the priority finding.

Common mistakes

  • Myth“Next.js or my host adds a Content-Security-Policy for me.”

    It does not. Next.js sends no CSP by default, and a platform default, if any, will not match your app. You have to define and tune the policy yourself.

  • Myth“My site is HTTPS, so I do not need these headers.”

    HTTPS encrypts transport. HSTS, CSP, and frame-ancestors solve different problems: downgrade attacks, script injection, and clickjacking. Transport encryption does not address any of those.

  • Myth“A CSP breaks my app, so I will skip it.”

    Start in report-only mode, which enforces nothing while reporting what would have been blocked, then tighten until clean and switch to enforcing. Skipping it entirely leaves every XSS uncontained.

  • Myth“X-XSS-Protection handles XSS for me.”

    That header is deprecated and ignored by modern browsers, and could introduce its own issues. Content-Security-Policy is its replacement and the actual mitigation.

Does SecureStartKit prevent this?

SecureStartKit already sends a real baseline from `next.config.ts` on every route: `Strict-Transport-Security` with preload, `X-Frame-Options: DENY`, `X-Content-Type-Options: nosniff`, `Referrer-Policy: strict-origin-when-cross-origin`, and `X-DNS-Prefetch-Control`. That closes clickjacking and MIME-sniffing out of the box. What it does not ship is a `Content-Security-Policy` or a `Permissions-Policy`, which are the two that take tuning and that would contain an XSS payload and lock down browser features. Add both, rolling the CSP out in report-only first, and you have the full set rather than the baseline.

Next.js security headers generator→

Frequently asked questions

What security headers should a Next.js app set?
At minimum Strict-Transport-Security, X-Frame-Options or a frame-ancestors directive, X-Content-Type-Options nosniff, and Referrer-Policy. Beyond the baseline, a Content-Security-Policy and a Permissions-Policy are the two highest-value additions.
Does Next.js add a Content-Security-Policy by default?
No. Next.js sends no CSP unless you configure one, either in next.config.ts headers or in middleware for a nonce-based policy. You have to define and tune it for your app.
How do I add a Content-Security-Policy in Next.js?
Add a Content-Security-Policy entry to the headers returned by next.config.ts, or set it from middleware if you need a per-request nonce. Start with Content-Security-Policy-Report-Only, review the violations, then switch to the enforcing header.
Are security headers enough to stop XSS?
No. A Content-Security-Policy contains and reduces the impact of XSS, but it is defense in depth. You still have to escape output and avoid unsafe sinks; the header is the safety net, not the fix.

References

  • MDN: Content-Security-Policy ↗
  • OWASP Secure Headers Project ↗
  • Next.js: headers configuration ↗
  • OWASP A02:2025 Security Misconfiguration ↗

Related weaknesses

  • XSS via dangerouslySetInnerHTMLdangerouslySetInnerHTML is set from user-controlled HTML or from Markdown-to-HTML output without first running the string through a sanitizer such as DOMPurify.
  • Exposed Supabase service_role KeyThe service_role key appears in browser DevTools under the Network tab or Sources panel, is readable in your built JavaScript bundle, is committed to a git repository, or is returned inside an API response body.
  • Next.js Middleware Auth BypassAuthorization is implemented only in middleware.ts or proxy.ts, with no second check at the data layer or inside the Server Action, route handler, or page component.

Defined terms

  • Content Security Policy
  • Cross-Site Scripting (XSS)
  • CORS

Go deeper

  • Next.js Security Headers: From Zero Defaults to A+
  • Security Headers Generator

Ship these defenses by default

SecureStartKit is a Next.js, Supabase, and Stripe starter with Row Level Security, Zod-validated Server Actions, verified Stripe webhooks, and backend-only data access already wired in. Start from a secure baseline instead of hardening by hand.

Get SecureStartKit→Browse all patterns
← Back to all security patterns