Milo Kits
Guide

Security headers for vibe-coded apps: copy-paste Netlify and Vercel configs

Add five headers to every response: Strict-Transport-Security, X-Content-Type-Options: nosniff, X-Frame-Options: DENY (or CSP frame-ancestors), Referrer-Policy and a Content-Security-Policy. On Netlify they go in a _headers file in your publish folder. On Vercel they go in vercel.json (or next.config.js for Next.js). Start the CSP in Report-Only mode, fix what it flags in the browser console, then switch it to enforcing. Copy-paste configs are below.

What each header does, in one line

HeaderWhat it stops
Strict-Transport-SecurityBrowsers falling back to plain HTTP on later visits (MDN)
Content-Security-PolicyInjected scripts loading or sending data to places you didn't approve (MDN)
X-Frame-Options / frame-ancestorsOther sites framing your pages for clickjacking
X-Content-Type-Options: nosniffBrowsers guessing file types and running something as script
Referrer-PolicyFull URLs (which may contain tokens or IDs) leaking to other sites
Permissions-PolicyPages using the camera, microphone or location unless you allow it

The OWASP Secure Headers Project keeps a longer reference if you want to go further.

Netlify: _headers

Create a plain text file called _headers (no extension) that ends up in your publish directory. For Vite apps that means putting it in public/_headers so it's copied into dist/ (Netlify docs). Replace YOUR-PROJECT with your Supabase project ref, and drop the Stripe parts if you don't use Stripe.

/* Strict-Transport-Security: max-age=31536000; includeSubDomains X-Content-Type-Options: nosniff X-Frame-Options: DENY Referrer-Policy: strict-origin-when-cross-origin Permissions-Policy: camera=(), microphone=(), geolocation=() Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' https://js.stripe.com; connect-src 'self' https://YOUR-PROJECT.supabase.co wss://YOUR-PROJECT.supabase.co https://api.stripe.com; frame-src https://js.stripe.com https://hooks.stripe.com; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; font-src 'self' data:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'

Vercel: vercel.json

Put this in vercel.json at your project root (Vercel docs).

{ "headers": [ { "source": "/(.*)", "headers": [ { "key": "Strict-Transport-Security", "value": "max-age=31536000; includeSubDomains" }, { "key": "X-Content-Type-Options", "value": "nosniff" }, { "key": "X-Frame-Options", "value": "DENY" }, { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" }, { "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" }, { "key": "Content-Security-Policy-Report-Only", "value": "default-src 'self'; script-src 'self' https://js.stripe.com; connect-src 'self' https://YOUR-PROJECT.supabase.co wss://YOUR-PROJECT.supabase.co https://api.stripe.com; frame-src https://js.stripe.com https://hooks.stripe.com; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; font-src 'self' data:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" } ] } ] }

Next.js: next.config.js

For Next.js on any host that runs the Next server, you can use the headers() config. With output: 'export' (a static export), this doesn't run, so set the headers at your host instead.

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

Rolling out CSP without breaking your app

  1. Start with Content-Security-Policy-Report-Only (as in the configs above). The browser reports violations in the console but blocks nothing.
  2. Click through your app: sign in, pay, upload, use every feature. Note each "violates the following Content Security Policy directive" message in the console.
  3. Add the domains you actually use (analytics, fonts, Supabase storage, Stripe). Stripe publishes the domains its scripts need in its security guide.
  4. When the console is clean, rename the header to Content-Security-Policy to enforce it.
  5. Avoid adding 'unsafe-eval', and 'unsafe-inline' for scripts. If something needs them, ask your AI coder for a different approach first.
Have your AI coder do it
Add security headers to my app for my host ([Netlify / Vercel / other]). Include Strict-Transport-Security, X-Content-Type-Options, X-Frame-Options DENY, Referrer-Policy strict-origin-when-cross-origin, Permissions-Policy, and a Content-Security-Policy in Report-Only mode that allows exactly what my app loads (look through the code for every external script, font, image, API and websocket domain, including Supabase and Stripe if used). No 'unsafe-eval'. Put the config in the right file for my host so it ships to production, and tell me how to switch CSP from Report-Only to enforcing once the console is clean.

Check it worked

Built-in hosting on some AI app builders may not let you set custom headers. If yours doesn't, check its docs, or deploy the same code to a host that does (Netlify, Vercel, Cloudflare Pages).

FAQ

Which security headers should every web app have?

At minimum: Strict-Transport-Security, Content-Security-Policy, X-Content-Type-Options: nosniff, a Referrer-Policy such as strict-origin-when-cross-origin, and frame protection through X-Frame-Options: DENY or the CSP frame-ancestors directive. Permissions-Policy is a good extra.

Where do I put security headers on Netlify?

In a plain text file named _headers in your site's publish directory (for Vite apps, put it in public/ so it's copied into dist/), or in [[headers]] blocks in netlify.toml.

Where do I put security headers on Vercel?

In vercel.json at the project root under a headers array, or for Next.js apps in the async headers() function in next.config.js.

Will a Content-Security-Policy break my app?

It can, if it blocks a script, API or font your app needs. Roll it out as Content-Security-Policy-Report-Only first, fix the violations shown in the browser console, then switch to the enforcing header.

How do I test my security headers?

Run curl -sI against your URL to see the raw headers, or use MDN HTTP Observatory or securityheaders.com for a graded report.

Sources

This guide is general information, not a security audit. Platforms change, so check the linked docs for current details. Spotted something out of date? Email hello@milokits.com.

More guides