Quick Answer
Security headers are HTTP response headers that instruct browsers on how to handle your site's content. Without them, your app is vulnerable to clickjacking, cross-site scripting, protocol downgrade attacks, and information leakage. AI-generated apps built with Bolt, Lovable, v0, and Cursor usually ship without most of them: even on Vercel, which adds HSTS automatically, the app gets no Content-Security-Policy, X-Frame-Options or other security headers unless you configure them.
Why Vibe-Coded Apps Have No Security Headers
Security headers live in HTTP responses, not in application logic. They're set in server configuration, middleware, or framework config files - layers that AI tools either skip or treat as optional boilerplate. When Bolt scaffolds a Next.js app or Lovable generates a React frontend, it produces components and API routes. It does not produce a next.config.js with a complete security headers block.
OWASP's Web Security Testing Guide has dedicated tests for HSTS, Content Security Policy and other HTTP security header misconfigurations. According to the HTTP Archive's 2024 Web Almanac, only 19% of hosts send a Content Security Policy header.
These headers cost almost nothing to add. A single configuration block can harden your entire application against multiple attack classes in under 10 minutes.
Before: A Typical Vibe-Coded App Response
Here is what an HTTP response from an AI-generated Next.js app deployed to Vercel looks like without any security header configuration. Vercel adds HSTS automatically (on a custom domain, without includeSubDomains or preload); everything else is missing:
$ curl -I https://your-vibe-app.vercel.app
HTTP/2 200
content-type: text/html; charset=utf-8
strict-transport-security: max-age=63072000; includeSubDomains; preload
x-vercel-id: iad1::abc123
cache-control: private, no-cache, no-store, max-age=0, must-revalidate
# Missing headers:
# - Content-Security-Policy
# - X-Frame-Options
# - X-Content-Type-Options
# - Referrer-Policy
# - Permissions-Policy
After: A Properly Configured Response
$ curl -I https://your-vibe-app.vercel.app
HTTP/2 200
content-type: text/html; charset=utf-8
strict-transport-security: max-age=63072000; includeSubDomains; preload
content-security-policy: default-src 'self'; script-src 'self' 'nonce-{nonce}'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://your-api.supabase.co
x-frame-options: DENY
x-content-type-options: nosniff
referrer-policy: strict-origin-when-cross-origin
permissions-policy: camera=(), microphone=(), geolocation=()
What Each Header Does
| Header | Check ID | What It Prevents | Recommended Value |
|---|---|---|---|
| Strict-Transport-Security | HDR-001 | Protocol downgrade attacks, forces HTTPS | max-age=63072000; includeSubDomains; preload |
| Content-Security-Policy | HDR-002 | XSS, data injection, script hijacking | Start with default-src 'self' and expand |
| X-Frame-Options | HDR-004 | Clickjacking attacks via iframes | DENY or SAMEORIGIN |
| X-Content-Type-Options | HDR-003 | MIME-sniffing attacks | nosniff |
| Referrer-Policy | HDR-005 | Leaking URL paths to third parties | strict-origin-when-cross-origin |
| Permissions-Policy | HDR-006 | Unauthorized access to camera, mic, location | camera=(), microphone=(), geolocation=() |
Adding Security Headers in Next.js
For Next.js apps - the most common target for Bolt, v0, and Cursor - security headers are added in next.config.js. This applies them to every response from your application:
// ✅ GOOD - next.config.js with security headers
const securityHeaders = [
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload',
},
{
key: 'X-Frame-Options',
value: 'DENY',
},
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin',
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()',
},
{
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
"script-src 'self' 'unsafe-eval' 'unsafe-inline'", // tighten after testing
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
"img-src 'self' data: https:",
"font-src 'self' https://fonts.gstatic.com",
"connect-src 'self'",
].join('; '),
},
];
/** @type {import('next').NextConfig} */
const nextConfig = {
async headers() {
return [
{
source: '/(.*)',
headers: securityHeaders,
},
];
},
};
module.exports = nextConfig;
Content Security Policy: Start Simple, Tighten Over Time
CSP is the most powerful header on the list, and also the most complex. A misconfigured CSP can break your application by blocking legitimate scripts, stylesheets, or API calls. The safe approach is to start permissive and tighten iteratively.
Use CSP report-only mode first: set Content-Security-Policy-Report-Only instead of Content-Security-Policy. This logs violations without enforcing them. Review the violation reports in your browser DevTools console or a reporting endpoint, then tighten the policy until violations stop.
For apps using Google Fonts (common in Lovable-generated designs), Supabase API calls, and Vercel Analytics, your CSP connect-src and font-src directives will need to explicitly allow those domains. The config above shows the Google Fonts entries as an example.
A CSP limits what an injected script can do, but the fix for cross-site scripting is in your code. See our guide to XSS in React and Next.js.
How to Find Missing Headers in Your App
Tools like VibeDoctor's Vibe Check automatically scan your deployed web app for missing security headers and flag which of HDR-001 through HDR-006 are absent from your HTTP responses. Free to sign up.
You can also check manually using the Mozilla Observatory (now part of MDN), SecurityHeaders.com, or by running curl -I your-domain.com and reviewing the response headers. Any production app scoring below B on the Mozilla Observatory has fixable issues that can be resolved in under an hour.
FAQ
Will adding a strict CSP break my Bolt or Lovable app?
Possibly, initially. Vibe-coded apps often use inline styles and scripts that CSP blocks by default. Start with Content-Security-Policy-Report-Only to identify violations, then either refactor inline code or add specific allowances. The unsafe-inline allowance for styles is a common concession for AI-generated apps using component libraries.
Does Vercel add any security headers automatically?
Only HSTS. Vercel sends Strict-Transport-Security automatically (max-age=63072000, plus includeSubDomains and preload on *.vercel.app), but it does not add CSP, X-Frame-Options or the other security headers. You must configure them explicitly in next.config.js or vercel.json. Vercel's documentation points Next.js projects to the next.config.js headers() approach.
What is clickjacking and how does X-Frame-Options prevent it?
Clickjacking embeds your site in an invisible iframe on an attacker's page, tricking users into clicking buttons they cannot see. X-Frame-Options: DENY prevents any site from embedding your pages in a frame. The modern equivalent is CSP frame-ancestors 'none', which overrides X-Frame-Options when both are present.
Is HSTS safe to add immediately?
Only if your site is already fully on HTTPS. HSTS tells browsers to never connect over HTTP again for the specified duration. If you add it while any part of your site is still accessible over HTTP, you may lock out users. Start with a short max-age (e.g., 300 seconds) and increase it to 63072000 (2 years) once you are confident.
Do these headers need to be set on API routes too?
For API responses, HSTS and X-Content-Type-Options are valuable. CSP and X-Frame-Options are less relevant for JSON API endpoints but harmless to include. The Next.js headers() configuration applies to all routes matching the source pattern, so using source: '/(.*)' covers both pages and API routes uniformly.