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.
Last updated
Shows up in: Next.js, Vercel
What it is
Security headers are instructions to the browser. The ones that matter for a small web app:
Content-Security-Policylimits where scripts, images, and frames can come from.frame-ancestors 'none'stops other sites from embedding you.X-Content-Type-Options: nosniffstops the browser from guessing that a file is HTML or script.Referrer-Policylimits how much of your URL leaks to other sites.strict-origin-when-cross-originis a reasonable default.Strict-Transport-Securitytells browsers to use HTTPS only. Set it only after the whole site works on HTTPS. Start with a shortmax-ageand increase it. Do not submit the domain to a preload list on day one.Permissions-Policyturns 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
Open your production or preview URL, inspect the response headers on the document, and look for the names above.
Search
next.configandvercel.jsonfor aheaderskey. If neither sets them, you are on framework defaults, which are not a policy.Load the site in an HTTPS window before you add HSTS. If any asset still needs
http://, fix that first.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 *.