Why AI-generated apps ship without a Content Security Policy
By Seaworthy · 25 June 2026
Photo by Denny Müller on Unsplash
Why AI-generated apps ship without a Content Security Policy
Ask an AI assistant to scaffold a Next.js app. You'll get routing, components, maybe even Tailwind config. What you almost certainly won't get is a Content Security Policy header. This isn't a small oversight. CSP is one of the most effective mitigations against cross-site scripting attacks, and it's missing from the vast majority of AI-generated codebases.
What CSP actually does
A Content Security Policy is an HTTP response header that tells the browser which sources are allowed to load scripts, styles, images, and other resources. If a script attempts to run from an origin not on your allowlist, the browser blocks it. That's the whole idea. No client-side code change required, no library to install, just a header.
XSS attacks work by injecting malicious scripts into your page, either through user input that gets rendered unsanitised, or by compromising a third-party resource you load. CSP doesn't prevent injection, but it prevents execution. That distinction matters because injection bugs are often subtle and hard to catch; execution blocks are a hard wall.
Without CSP, a successful XSS attack can exfiltrate session tokens, perform actions on behalf of the user, or redirect them to a phishing page. With a reasonable CSP in place, the attacker injects their script and the browser quietly refuses to run it.
Why AI tools skip it
I don't think there's a sinister reason. CSP is genuinely awkward to configure. A policy that's too strict breaks your own app. A policy that's too loose is close to useless. Getting it right requires understanding every script and style source your app uses, including third-party analytics, fonts, payment widgets, and CDNs.
AI assistants are optimised to produce working code quickly. CSP requires an iterative audit-and-tighten workflow that doesn't fit neatly into "generate this component for me". So it gets left out, silently, every time.
The result is that vibe-coded apps, assembled from AI-generated pieces, almost never have one.
A starter policy for Next.js
In Next.js, you set headers in next.config.ts:
// next.config.ts
import type { NextConfig } from 'next';
const cspHeader = [
"default-src 'self'",
"script-src 'self' 'unsafe-inline'", // tighten this once you know your sources
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self' https://fonts.gstatic.com",
"connect-src 'self'",
"frame-ancestors 'none'",
"form-action 'self'",
"base-uri 'self'",
].join('; ');
const nextConfig: NextConfig = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: cspHeader,
},
],
},
];
},
};
export default nextConfig;
This is deliberately cautious. unsafe-inline on scripts is weaker than a nonce-based approach, but it's a working starting point that won't immediately break inline scripts. Once you have this in place, open the browser console and look for CSP violation reports. Each violation tells you a source you need to explicitly allow or a piece of inline code you need to refactor.
The goal is to progressively remove unsafe-inline from script-src by replacing inline scripts with nonces or hashes. Next.js 13+ supports nonce-based CSP with middleware.
A starter policy for Express
// app.js (Express)
const helmet = require('helmet');
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
frameAncestors: ["'none'"],
formAction: ["'self'"],
baseUri: ["'self'"],
},
})
);
Helmet makes this simple. If you're not using Helmet already, add it. It sets several other useful security headers too.
The report-only mode trick
Before enforcing a policy on a production app, use Content-Security-Policy-Report-Only instead. Violations are logged (or sent to a reporting endpoint) but not blocked. This lets you audit what your app is actually doing without breaking anything for users.
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
Run this for a week. Collect violations. Then write your enforced policy from what you learned.
What to tighten next
Once you have the basics in place, look at:
- Replacing
unsafe-inlinewith nonces or hashes inscript-src - Locking
connect-srcto specific API domains rather thanhttps: - Adding
upgrade-insecure-requestsif you're confident about HTTPS - Setting
object-src 'none'to block Flash and plugin-based attacks
CSP isn't a one-and-done change. It's a configuration you maintain alongside your app. When you add a new analytics provider or CDN font, you update the policy. That ongoing discipline is exactly what AI code generation skips.
Seaworthy flags missing or misconfigured CSP headers as part of its configuration category checks.
This article was generated by AI and summarises publicly available sources.
Seaworthy scans your repo for the issues covered in articles like this one.
Security gaps, exposed secrets, and misconfigurations — caught before you deploy. Free to run, no account needed.