Skip to content

Guides · VibeVigil

How to Add Security Headers to Your Lovable, Bolt, v0 or Cursor App

What security headers are, how to check if your AI-built app has them, and copy-paste setups for Vercel, Netlify, Cloudflare Pages and Next.js, plus a prompt for your coding agent.

A security scanner told you your app is "missing security headers." Or you just want to know what that means. This guide explains it in plain words, shows you how to check your own app, and gives you setups you can copy.

You don't need to be a developer.

The short answer

  • Security headers are small notes your server sends with every page. They tell the browser what it is allowed to do with your site.
  • They don't fix a leaky database or an exposed key. They do block some common attacks, like other sites loading your app inside a hidden frame, or the browser running scripts it shouldn't.
  • Most AI app builders don't add them for you. You add them once, where your app is hosted.
  • Start with the five "safe" headers below. Add a Content Security Policy last, and test it first, because a wrong one can break your app.

What each header does

HeaderWhat it doesSafe starting value
Strict-Transport-SecurityTells browsers to always use HTTPS for your sitemax-age=31536000; includeSubDomains
X-Content-Type-OptionsStops the browser from guessing file types (a trick attackers use)nosniff
X-Frame-OptionsStops other sites from showing your app inside a frame (clickjacking)DENY
Referrer-PolicyLimits what URL info is shared when users click a link to another sitestrict-origin-when-cross-origin
Permissions-PolicyTurns off browser features you don't use, like the camera or microphonecamera=(), microphone=(), geolocation=()
Content-Security-PolicyLists where scripts, styles, images and data may load fromDepends on your app (see Step 4)

You can read the official details for each one on MDN and in the OWASP Secure Headers Project.

Careful with two of them:

  • If your app uses the camera, microphone or location, don't block that feature in Permissions-Policy. Use camera=(self) instead of camera=().
  • If your app is meant to be shown inside another site (for example, an embeddable widget), don't use X-Frame-Options: DENY. Use the frame-ancestors rule in your Content Security Policy to list the sites that may embed it.

Step 1: Check what your app sends today

Option A: In your browser (no tools needed)

  1. Open your live app in Chrome, Edge or Firefox.
  2. Right-click the page and choose Inspect, then open the Network tab.
  3. Reload the page. Click the first row (your page's address).
  4. Look under Response Headers. Do you see the headers from the table above?

Option B: In a terminal

curl -sI https://your-app.com

This prints the headers your server sends. Look for the names in the table.

Option C: A free public checker

Mozilla's HTTP Observatory grades your headers and explains each missing one. Only check sites you own or have permission to test.

Step 2: Find where your app is hosted

Headers are set by the host, not by the page code. So the fix depends on where your app lives:

  • Vercel (common for v0 and Cursor apps): use vercel.json, or next.config.js for Next.js apps.
  • Netlify (used by many Bolt and Vite apps): use a _headers file or netlify.toml.
  • Cloudflare Pages: use a _headers file.
  • Your AI builder's own hosting (for example, a lovable.app or bolt.host address): check that builder's docs or settings to see if you can set custom headers. If you can't, you can deploy the same app to a host that allows it, or put a custom domain behind a service that can add headers.

Not sure? Look at the address your app runs on, or ask your coding agent: "Where is this app deployed, and how do I set HTTP response headers there?"

Step 3: Add the safe headers

Pick the one setup that matches your host. All of these add the same five headers to every page.

Vercel (any framework): vercel.json

Put this file in the root of your project. See Vercel's 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=()" }
      ]
    }
  ]
}

If you already have a vercel.json, add the headers part to it. Don't make a second file.

Next.js: next.config.js

See the Next.js headers docs.

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 }];
  },
};

If your config file already has other settings, merge the headers() function into it.

Netlify or Cloudflare Pages: _headers

Put a file named _headers (no file extension) in the folder that gets published. For Vite apps that is usually public/, so it gets copied into dist/. See Netlify's docs and Cloudflare's docs.

/*
  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=()

Deploy, then repeat Step 1 to confirm the headers show up.

Step 4: Add a Content Security Policy (carefully)

A Content Security Policy (CSP) is the strongest header and the easiest to get wrong. It lists every place your app may load code and data from. If you forget one (say, Supabase, Stripe or your analytics), that part of your app stops working.

So do it in two steps:

1. Start in "report only" mode. This header warns you in the browser console but blocks nothing:

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

Replace YOUR-PROJECT with your Supabase project ID. Add any other services your app talks to (for example, https://js.stripe.com in script-src, and Stripe's domains in frame-src).

2. Use the app for a few minutes. Sign in, upload, pay, and open every main page. Then open the browser console and look for CSP warnings. Each warning names a blocked address. Add the ones you trust to the policy.

When there are no more warnings, rename the header from Content-Security-Policy-Report-Only to Content-Security-Policy. Now it is enforced.

You can read the full rules on MDN's Content-Security-Policy page.

Common mistakes

  • Adding headers in the HTML instead of the server. Most of these headers only work when the server sends them. A <meta> tag can't set X-Frame-Options or Strict-Transport-Security.
  • Turning on HSTS before HTTPS works everywhere. Strict-Transport-Security makes browsers refuse plain HTTP for a long time. Make sure every subdomain has a working certificate first. If some don't, leave out includeSubDomains.
  • Copying a strict CSP from someone else. Every app loads from different places. A copied policy will likely break yours.
  • Thinking headers make the app "secure." Headers are one layer. Your database rules (RLS) and keeping secret keys out of the browser matter more. See our guides on checking Supabase RLS and finding leaked API keys.

Prompt for your coding agent

Paste this into Lovable, Bolt, v0, Cursor or a similar tool:

Add HTTP security headers to this app.

1. Tell me where the app is deployed and which file sets response headers there (vercel.json, next.config.js, a _headers file, netlify.toml, or other). If the current host can't set custom headers, tell me plainly and stop.
2. Add these headers to every route:
   - Strict-Transport-Security: max-age=31536000; includeSubDomains
   - X-Content-Type-Options: nosniff
   - X-Frame-Options: DENY (skip this and tell me if the app is meant to be embedded in other sites)
   - Referrer-Policy: strict-origin-when-cross-origin
   - Permissions-Policy: camera=(), microphone=(), geolocation=() (allow with (self) any feature the app actually uses)
3. Add a Content-Security-Policy-Report-Only header (NOT enforced yet). Build it from the services this app really uses: list every external domain for scripts, styles, images, fonts, API calls (including Supabase https and wss), and frames.
4. Don't remove or change any existing headers or config without telling me.
5. After deploying, show me the command to check the headers (curl -sI <my-url>) and what I should see.

Quick checklist

  • [ ] I checked my live app's response headers.
  • [ ] I know where my app is hosted and where headers are set.
  • [ ] HSTS, nosniff, frame protection, referrer policy and permissions policy are on.
  • [ ] A Content Security Policy runs in report-only mode, and I fixed the warnings.
  • [ ] After testing, the CSP is enforced.
  • [ ] I also checked RLS and exposed keys, because headers alone don't protect data.

Scan your app free. It checks the public page of a site you own. No account.

How to Add Security Headers to Your Lovable, Bolt, v0 or Cursor App · VibeVigil