Abbi Labs

Guides

Security headers for Lovable, Bolt, v0 and Cursor apps: CSP, HSTS and clickjacking

By Pritesh Ghodge, security engineer · Last updated 30 October 2026

Want to see which headers your live app is missing?

The Abbi Labs quick check reads your home page's response headers (CSP, HSTS, clickjacking protection), and also looks for secret keys in your JavaScript, exposed .env and .git files, and Supabase tables readable with your public key. No sign-up.

Scan your app free

Weekly AI-app security checklist

One short email a week: a practical check for apps built with Supabase, Next.js and AI tools (RLS, leaked keys, headers and more). Unsubscribe anytime.

Security headers are instructions your server sends with every page, telling the browser what it may and may not do with it. AI builders rarely add them, because the app works fine without them. They won't save you from a leaked key or an open table, but they limit the damage when something else goes wrong, and they're one of the first things any security review or customer questionnaire looks at.

One thing to know up front: headers come from whatever serves your site, not from your React code. A Vite or Lovable app is static files; the host adds the headers. So the fix depends on where your app is hosted.

Step 1: see what your site sends today

curl -sI https://yoursite.com/ | grep -iE \
  'content-security|strict-transport|x-frame|x-content-type|referrer-policy|permissions-policy'

Run it against the domain your users actually visit (your custom domain, not only the .lovable.app or .vercel.app URL). No curl? DevTools → Network → reload → click the first request → Response Headers.

Step 2: the headers that matter, in order

Strict-Transport-Security (HSTS)

Tells browsers to only ever use HTTPS for your domain, so nobody on the same café Wi-Fi can downgrade a visitor to plain HTTP. Many managed hosts send it already: Vercel, for example, adds max-age=63072000 to custom domains by default. If yours doesn't:

Strict-Transport-Security: max-age=63072000; includeSubDomains

Only add preload if every subdomain is HTTPS and you mean to submit the domain to the browser preload list. It's hard to undo.

Clickjacking protection: X-Frame-Options or frame-ancestors

Without it, any site can load your app in an invisible iframe and trick a logged-in user into clicking buttons in it ("delete account", "send invite"). Unless you deliberately let other sites embed you:

X-Frame-Options: DENY
# or, inside your CSP:
Content-Security-Policy: frame-ancestors 'none'

This only works as a real HTTP header. Browsers ignore frame-ancestors in a <meta> tag.

Content-Security-Policy (CSP)

A list of where scripts, styles, images and network requests may come from. If someone manages to inject a script (a malicious npm package, an unescaped user field), the CSP decides whether it can load more code or send data to their server. It's the most useful header and the one most likely to break things, so roll it out in report-only mode first. A starting point for a typical Vite + Supabase + Stripe app:

Content-Security-Policy-Report-Only: default-src 'self';
  script-src 'self' https://js.stripe.com;
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  font-src 'self' data:;
  connect-src 'self' https://YOUR-PROJECT.supabase.co wss://YOUR-PROJECT.supabase.co;
  frame-src https://js.stripe.com https://checkout.stripe.com;
  frame-ancestors 'none'; base-uri 'self'; form-action 'self'

(Written over several lines here for reading; the real header is one line.) Load every page of your app with DevTools open. Each blocked request shows up as a console warning naming the directive. Add the origins you actually use, then rename the header to Content-Security-Policy to enforce it. Next.js apps with inline scripts usually need a nonce-based policy; Next.js documents how to set one up in middleware.

The two cheap ones

X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin

nosniff stops browsers guessing a file's type (so an uploaded "image" isn't run as a script). The referrer policy stops full URLs, which sometimes carry tokens or IDs, leaking to other sites when users click a link.

Step 3: add them on your host

Vercel (any framework): vercel.json

{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        { "key": "X-Frame-Options", "value": "DENY" },
        { "key": "X-Content-Type-Options", "value": "nosniff" },
        { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
        { "key": "Content-Security-Policy-Report-Only", "value": "default-src 'self'; frame-ancestors 'none'" }
      ]
    }
  ]
}

Next.js: next.config.js

module.exports = {
  async headers() {
    return [{
      source: "/(.*)",
      headers: [
        { key: "X-Frame-Options", value: "DENY" },
        { key: "X-Content-Type-Options", value: "nosniff" },
        { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
      ],
    }];
  },
};

Netlify (Bolt can deploy here too): a _headers file

Put a file called _headers in the folder that gets published (for Vite, public/, so it's copied into dist/):

/*
  X-Frame-Options: DENY
  X-Content-Type-Options: nosniff
  Referrer-Policy: strict-origin-when-cross-origin
  Strict-Transport-Security: max-age=63072000; includeSubDomains

Lovable hosting

Lovable's hosting docs don't describe a way to set your own response headers, and a vercel.json or _headers file the AI writes won't be read by Lovable's hosting. Realistic options:

The same applies to Bolt, Replit and other builders: find out which host actually serves your app, then use that host's header settings.

A prompt for Cursor, Bolt or Lovable (when you're on Vercel or Netlify):

Security fix: add HTTP security headers for production.
1. Tell me which host serves this app and where its header config lives.
2. Add X-Frame-Options: DENY, X-Content-Type-Options: nosniff and
   Referrer-Policy: strict-origin-when-cross-origin for all routes.
3. Add a Content-Security-Policy-Report-Only header that allows only
   the origins this app actually uses (list them for me).
Don't change app code. Show the diff before applying.

Step 4: verify on the live site

Re-run the curl from step 1 against your production domain after the deploy. Config files in the repo prove nothing until the live response has the headers. Check a deep route too (for example /dashboard): a header rule with the wrong path pattern can cover the home page and miss the rest.

Keep them there

Headers disappear quietly: a move to a new host, a rewritten config file, an AI edit that "simplifies" vercel.json. Abbi Labs checks your home page headers in the free quick check, alongside the bigger risks (secret keys in your JavaScript, Supabase tables readable with your public key, exposed .env and .git files), and Monitor re-scans weekly and after every deploy, emailing you only when something new appears.

Run a free scan

FAQ

How do I check which security headers my site sends?

Run curl -sI https://yoursite.com/ from a terminal and look for Content-Security-Policy, Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options and Referrer-Policy in the response. Check your custom domain, not just the platform URL, because they can differ. Browser DevTools → Network → click the page request → Response Headers shows the same thing.

Will adding a Content-Security-Policy break my app?

It can, which is why most people skip it. A strict policy blocks anything not on its list: your Supabase project, Stripe, fonts, analytics. Start with the Content-Security-Policy-Report-Only header, browse your app and read the console warnings, add the missing origins, then switch to the enforcing header.

Can I set security headers on Lovable hosting?

Lovable's hosting docs don't describe a setting for custom response headers. Asking the AI to 'add security headers' may create a vercel.json or _headers file that Lovable's own hosting doesn't read. Your options: put a proxy such as Cloudflare in front of your custom domain and add response headers there, or deploy the same build to a host where you control headers (Vercel, Netlify, Cloudflare Pages). A CSP meta tag in index.html is a partial fallback: it can't set frame-ancestors, HSTS or X-Frame-Options.

Are missing security headers a vulnerability?

Usually not on their own. They're a second layer: a CSP limits what an injected script can do, HSTS stops downgrade-to-HTTP tricks, and frame protection stops your pages being embedded for clickjacking. Fix leaked keys and open database tables first; headers come after.

Does Abbi Labs check security headers?

Yes. The free quick check on the home page (no sign-up) reads the response headers of your home page and reports a missing Content-Security-Policy, missing Strict-Transport-Security, and missing clickjacking protection (no X-Frame-Options and no CSP frame-ancestors). It checks the home page only, not every route.

Related guides

Weekly AI-app security checklist

One short email a week: a practical check for apps built with Supabase, Next.js and AI tools (RLS, leaked keys, headers and more). Unsubscribe anytime.