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

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

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.