Skip to content
ShipSecurelySecurity reference
Menu
Your tool here — sponsor ShipSecurely

Issues

Missing security headers

Headers tell the browser not to sniff content, not to load you in a frame, and which scripts are allowed. They limit damage, and they do not replace auth.

Share link · Share on X

Last updated

mediumConfiguration

Shows up in: Next.js, Vercel

Markdown version

What it is

Security headers are instructions to the browser. The ones that matter for a small web app:

  • Content-Security-Policy limits where scripts, images, and frames can come from. frame-ancestors 'none' stops other sites from embedding you.
  • X-Content-Type-Options: nosniff stops the browser from guessing that a file is HTML or script.
  • Referrer-Policy limits how much of your URL leaks to other sites. strict-origin-when-cross-origin is a reasonable default.
  • Strict-Transport-Security tells browsers to use HTTPS only. Set it only after the whole site works on HTTPS. Start with a short max-age and increase it. Do not submit the domain to a preload list on day one.
  • Permissions-Policy turns off browser features you do not use, such as camera and microphone.

X-Frame-Options: DENY is the older frame control. CSP frame-ancestors is the modern one. Sending both is fine.

X-XSS-Protection is obsolete. Do not add it as your fix. OWASP's HTTP headers guidance covers the current set. A scanner that only grades headers will still pass a site with no RLS. Use headers and the other guides.

How to check in 5 minutes

  1. Open your production or preview URL, inspect the response headers on the document, and look for the names above.

  2. Search next.config and vercel.json for a headers key. If neither sets them, you are on framework defaults, which are not a policy.

  3. Load the site in an HTTPS window before you add HSTS. If any asset still needs http://, fix that first.

  4. If you add a strict script-src, load the app and watch the console. Next.js needs room for its own scripts. A policy that blanks the page is too tight for a first pass. Tighten from a policy you have tested.

How to fix

Next.js

const securityHeaders = [
  { key: "X-Content-Type-Options", value: "nosniff" },
  { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
  { key: "X-Frame-Options", value: "DENY" },
  { key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
  {
    key: "Content-Security-Policy",
    value: "frame-ancestors 'none'; base-uri 'self'; object-src 'none'",
  },
];

Return those from the headers() function in next.config. Add HSTS only on the HTTPS deployment:

Strict-Transport-Security: max-age=86400

A full script-src is worth doing, and it is specific to how your app loads scripts. Start with the headers above, then add script rules and click through the app. OWASP's CSP cheat sheet is the reference for the directives. Do not copy a policy from a blog if you have not loaded your pages with it.

Vercel

vercel.json can set the same headers. Do not set them twice in conflict. One place is enough. Preview deployments should get the same headers as production, minus a preload-level HSTS if previews move around.

Supabase and Firebase

These headers protect the site you host, not the database. They do nothing for a publishable key and an open table. Set the headers and still fix RLS or rules.

Copy-paste prompt

Paste this into Cursor, Claude Code, or whichever agent is editing the repo. Read the diff before you merge it.

You are adding security headers. Do not add features.

Add response headers on all routes:
- X-Content-Type-Options: nosniff
- Referrer-Policy: strict-origin-when-cross-origin
- X-Frame-Options: DENY
- Permissions-Policy that disables camera, microphone, and geolocation unless this app uses them
- Content-Security-Policy with frame-ancestors 'none', base-uri 'self', and object-src 'none'

Do not add X-XSS-Protection.
Do not add Strict-Transport-Security unless I confirm the site is served only over HTTPS. If you add it, use max-age=86400 and do not include preload.

Do not set a script-src that you have not verified against this app. If you add one, say exactly what I should click to confirm the app still loads.

Show the config diff.

Questions

A header scanner gave me an F. Is the app hacked?

No. It means the browser is missing those instructions. Fix the headers, and do not treat a high score as a clean bill for auth or the database.

Will a CSP break my analytics or chat widget?

A strict script policy will block third-party scripts until you allow their hosts. That is the point. Add the host when you mean to, instead of starting from script-src *.

Sources

Related