All platforms
Security headers for AI-built apps: CSP, HSTS and X-Frame-Options
Apps built with Lovable, Bolt, v0, Replit or Cursor usually ship without security headers. Add HSTS, CSP, X-Frame-Options and three more in minutes.
Updated 2026-10-27
AI app builders get your product working. They rarely add the response headers that tell a browser how strictly to treat your site. Missing headers are not a data breach on their own, but they remove cheap layers of protection against cross-site scripting, clickjacking and downgrade attacks. They are also the easiest findings to fix, because you change one config file and redeploy.
Security headers are set by whatever serves your files: Vercel, Netlify, Cloudflare Pages, a Node server, or the platform that hosts your Lovable or Bolt project. The generated code almost never touches them, so you have to add them where your host allows it.
The six headers worth setting
Strict-Transport-Security(HSTS) tells the browser to use HTTPS only for your domain. Start withmax-age=31536000; includeSubDomains.Content-Security-Policy(CSP) lists where scripts, styles, images and connections may come from. It limits the damage of injected scripts.X-Frame-Options: DENY, or the CSP directiveframe-ancestors 'none', stops other sites from embedding your app in an invisible frame (clickjacking).X-Content-Type-Options: nosniffstops the browser from guessing file types.Referrer-Policy: strict-origin-when-cross-originkeeps full URLs, which can contain tokens or ids, out of third-party logs.Permissions-Policyswitches off browser features you do not use, such as camera and microphone.
Check what you send today
Open your deployed app, then DevTools, Network, click the first document request and read Response Headers. Or from a terminal:
curl -sI https://your-app.example | grep -iE "strict-transport|content-security|x-frame|x-content-type|referrer-policy|permissions-policy"
No output means none of them are set. The free Vibe Scan does the same check and shows the result as a grade from A to F.
Vercel (vercel.json)
{
"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=()" }
]
}
]
}
Netlify and Cloudflare Pages (public/_headers)
Both read a plain _headers file from the build output. In a Vite project, put it in public/ so it is copied into dist/.
/*
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=()
Next.js (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" },
];
module.exports = {
async headers() {
return [{ source: "/(.*)", headers: securityHeaders }];
},
};
Add a CSP without breaking your app
A CSP is the one header that can break a working page, so roll it out in report-only mode first. The browser logs what it would have blocked, but blocks nothing.
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://*.supabase.co wss://*.supabase.co; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'
Load the app, click through the main flows with the console open, and add each legitimate origin the console reports: your analytics script, Stripe, a font host, Firebase endpoints. When the console is quiet, rename the header to Content-Security-Policy.
Two rules keep a CSP useful. Avoid 'unsafe-inline' and 'unsafe-eval' in script-src where you can, because they cancel most of the protection. And do not add * or a whole scheme like https: to script-src. In Next.js, a per-request nonce is the supported way to allow its inline scripts; the Next.js docs describe it under "Content Security Policy".
Platform notes
- Lovable, Bolt and Replit hosting: you may not be able to set headers on the default domain. Put the app behind your own host or a CDN you control (for example Cloudflare, with a Transform Rule that adds response headers), or export the project to Vercel, Netlify or Cloudflare Pages and set them there.
- Meta tags: a
<meta http-equiv="Content-Security-Policy">tag works for CSP but cannot doframe-ancestors, and HSTS is not supported as a meta tag. A real header is better. - HSTS preload: do not submit your domain to the preload list until every subdomain really works over HTTPS. That step is hard to undo.
Verify, then show it off
Redeploy, run the free scan again, and confirm the header findings are gone. If your grade is A or B, the result page gives you a badge snippet to embed in your README or footer. It links to your scan result and shows visitors you checked.
FAQ
Do security headers protect my database?
No. Headers harden the browser side. Your data is protected by row level security and by keeping secret keys on the server. See the Supabase RLS guide.
Will HSTS lock me out of my own site?
Only if your HTTPS breaks. Browsers will refuse plain HTTP for the max-age period. Test on the live domain with a short max-age such as 300 first, then raise it.
Do I need X-Frame-Options if I have frame-ancestors?
Modern browsers follow frame-ancestors. Sending both is harmless and covers older browsers.
Why is my CSP report-only still showing warnings?
Something on the page loads from an origin you have not listed. Add it if you trust it, and remove the script if you do not recognise it.