Launch and other stacks

Security headers for AI-built apps (Lovable, Bolt, Vercel, Netlify)

Security headers are a handful of lines your host adds to every response, telling browsers to use HTTPS only, which scripts may run, who may frame the page and how much of your URLs to share. Apps built with Lovable, Bolt or Cursor usually ship with none of them, because headers live in hosting config rather than in the code a generator writes. The fix is one file per host, and this guide gives you that file.

TL;DR

  • Send five headers on every response: Strict-Transport-Security, Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, plus HTTPS forced at the host.
  • Vercel: a headers block in vercel.json. Netlify and Cloudflare Pages: a _headers file in your publish folder. A proxied Cloudflare site: Transform Rules.
  • Lovable hosting: its docs don’t describe a headers setting (checked 10 Nov 2026); put the custom domain behind Cloudflare, or use meta tags for the two headers that allow it.
  • Roll out the Content-Security-Policy in report-only mode first; it is the one header that can break your app.
  • Check the result with curl -sI or the free headers check.

What security headers do

Every time a browser loads your page, your host sends headers before the HTML. A few of them change how the browser treats your site: whether it may ever use plain HTTP, which scripts it will run, whether another site may show your app inside a frame. None of them changes your code, and none of them fixes a bug on its own. They make the common attacks on a web app harder or impossible, and they cost nothing once set.

For a typical AI-built app, a React front end on Supabase, the stakes are concrete. supabase-js keeps the signed-in session in localStorage by default, so any script that gets onto your page can read it and act as the user. Headers are what stop that script from loading, or from sending what it reads anywhere.

Why AI-built apps ship without them

Headers are set by the host, in a file like vercel.json or _headers, or in a dashboard. App builders generate the app’s code and leave hosting at its defaults. Previews look fine, because nothing visibly breaks without headers, and some builders show your app inside a frame in their editor, so a header that blocks framing would break their own preview. The result: the app works, and the hosting config was never written.

The exception is HTTPS itself. Vercel, Netlify, Cloudflare and Lovable all issue certificates automatically; problems appear with custom domains, proxies and old DNS records.

The starter set

These are the headers to send on every response, what each one does, and the finding page with the details and the self-check.

HeaderValue to start withWhat it stopsFinding
HTTPS redirect301 or 308 from http:// to https://Pages read or changed on the networkPL-027
Strict-Transport-Securitymax-age=63072000; includeSubDomainsThe one plain-HTTP request before the redirectPL-029
Content-Security-PolicySee below; report-only firstInjected scripts running or sending data outPL-030
X-Frame-OptionsDENY (plus frame-ancestors 'none' in the CSP)Clickjacking through an invisible framePL-031
X-Content-Type-OptionsnosniffUploaded files run as scripts or pagesPL-032
Referrer-Policystrict-origin-when-cross-originFull URLs, with any tokens in them, sent to other sitesPL-036

Three more checks belong with them, though they aren’t headers you add: a valid certificate that isn’t about to expire (PL-028), cookies with Secure, and HttpOnly on session cookies (PL-033), and no scripts or styles loaded over http:// (PL-034). And one to remove: Server or X-Powered-By headers that announce software versions (PL-035).

A Content-Security-Policy for a Supabase app

The CSP is the only header here that can break your app, because it blocks everything it doesn’t list. Start with this policy for a Vite or React app on Supabase, with your project ref in place of YOUR-PROJECT, and send it as Content-Security-Policy-Report-Only: the browser then blocks nothing and prints each violation in the DevTools console.

starter policy (one line in the real header)
default-src 'self';
  script-src 'self';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: blob: https://YOUR-PROJECT.supabase.co;
  font-src 'self' data:;
  connect-src 'self' https://YOUR-PROJECT.supabase.co wss://YOUR-PROJECT.supabase.co;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
  object-src 'none'

Use every part of the app for a few days, including sign-in, uploads and payments. For each console violation, decide whether the source is one you meant to use and add it to the right directive: Google Fonts needs https://fonts.googleapis.com in style-src and https://fonts.gstatic.com in font-src; an analytics tag needs its host in script-src and connect-src; a payment form often needs its host in frame-src. When the console is quiet, rename the header to Content-Security-Policy.

Don’t add 'unsafe-inline' to script-src to make an error go away. It lets injected inline scripts run, which is exactly what the policy exists to stop. Move the inline script into a file, or use a nonce if your framework supports one.

The full set, per host

Each block below sends the whole starter set, with the CSP in report-only mode. Add it, redeploy, then check it (next section). Bolt projects use whichever host they’re deployed to: a Bolt app deployed to Netlify uses the Netlify file.

Vercel

vercel.json at the root of the project. Vercel already redirects HTTP to HTTPS on domains added to the project.

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

A Next.js app can return the same list from headers() in next.config.js; set poweredByHeader: false there too.

Netlify

A file named _headers in the publish folder. With Vite, put it in public/ and the build copies it into dist/. Netlify redirects HTTP to HTTPS once the domain’s certificate is issued.

public/_headers
/*
  Strict-Transport-Security: max-age=63072000; includeSubDomains
  Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://YOUR-PROJECT.supabase.co; font-src 'self' data:; connect-src 'self' https://YOUR-PROJECT.supabase.co wss://YOUR-PROJECT.supabase.co; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'
  X-Frame-Options: DENY
  X-Content-Type-Options: nosniff
  Referrer-Policy: strict-origin-when-cross-origin

Cloudflare

Cloudflare Pages or Workers static assets: the same _headers file as Netlify, in your build output.

Any site proxied through Cloudflare (orange cloud), wherever it is hosted:

  1. SSL/TLS → Overview: set the mode to Full (strict).
  2. SSL/TLS → Edge Certificates: turn on Always Use HTTPS, and turn on HTTP Strict Transport Security (HSTS) with a max-age of 12 months or more.
  3. Rules → Transform Rules: create a Response Header Transform Rule for your hostname that sets the four remaining headers from the table above: Content-Security-Policy-Report-Only, X-Frame-Options, X-Content-Type-Options and Referrer-Policy.

Lovable

Lovable issues and renews the certificate for a connected custom domain. Its documentation (checked 10 Nov 2026) doesn’t describe a way to add your own response headers on Lovable hosting. Two routes work today:

  1. Cloudflare in front. When you connect the custom domain, choose “Uses Cloudflare or similar proxy”, then follow the Cloudflare tab. This is the only way to send HSTS, X-Frame-Options and nosniff, which browsers accept only as real headers.
  2. Meta tags in index.html, for the two policies that allow it. A meta-tag CSP can’t use frame-ancestors or report-only mode, so test it in a local build first.
index.html, inside <head>, before any script
<meta name="referrer" content="strict-origin-when-cross-origin">
<meta http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://YOUR-PROJECT.supabase.co; font-src 'self' data:; connect-src 'self' https://YOUR-PROJECT.supabase.co wss://YOUR-PROJECT.supabase.co; base-uri 'self'; form-action 'self'; object-src 'none'">

If Lovable adds a headers setting, the values in the table above are the same wherever you set them.

Check the result

After you deploy, read the headers back from the live site. curl -sI prints only the response headers:

check-headers.sh
curl -sI https://your-app.com | grep -iE 'strict-transport|content-security|x-frame|x-content-type|referrer-policy|set-cookie|^server|x-powered-by'

# and the plain address should redirect to https://
curl -sI http://your-app.com | head -n 3

Or type your address into the free headers check. It loads your public home page once, like any visitor, and grades HTTPS, the certificate, these headers, cookie flags and mixed content from A to F, with the fix for each finding. No sign-up, and the result isn’t stored.

Then open the app in a browser with DevTools → Console, and work through any CSP reports before you switch the policy from report-only to enforced.

What headers don’t do

Headers protect the browser, not your database. A perfect A grade says nothing about who can read your Supabase tables: a missing row level security policy leaks data to anyone with your public key, whatever headers your site sends. That is a separate check, made by rebuilding the database from your migrations. See RLS policies explained.

Headers also can’t fix a script you load on purpose that turns out to be malicious, or a cross-site scripting bug that a lax CSP allows. They narrow what an attacker can do; they don’t replace fixing the bug.

Keep them in place

Headers disappear in ordinary changes: a new host, a rewritten vercel.json, a domain moved behind a proxy, a renewal that silently fails. PatchLabs Live posture re-checks a domain you verify every day, loading only the public home page like any visitor, and alerts your team when the grade drops or a new finding appears. It never touches your database.

The checks behind this guide, each with example code, a self-check and the fix.

Free check

Check your site’s headers, free.

Type your site’s address. The free headers check loads your public home page once, like any visitor, and grades HTTPS, the certificate, security headers, cookie flags and mixed content, with the fix for each finding.

Free, no sign-upLoads your public home page only

Written by Manraj Singh Dhillon. Last checked 10 Nov 2026. Code samples are synthetic and run against your own project; nothing here names a real app. Spotted a mistake? Tell us.