← All posts
CSPContent Security PolicyNext.jsvibe codingsecurity headers

Missing CSP Headers Fix: Secure Your Vibe-Coded Next.js App

O

OverMCP Team

Quick answer

To fix missing CSP headers in a vibe-coded Next.js app, add a Content Security Policy header via next.config.js or middleware to control which scripts, styles, and resources can load. Start with a restrictive policy, test thoroughly, and adjust for inline scripts and external resources your AI-generated code uses. Use a security headers checker to verify your policy after deployment.

What to check first

Before writing any code, audit your app to understand what resources it loads. Vibe-coded apps often include inline scripts, external fonts, analytics, or third-party widgets that CSP can block.

  • List all external domains your app loads from (Google Fonts, Stripe, analytics, CDNs).
  • Identify inline scripts and styles — AI tools like Cursor and Bolt.new often generate <script> tags without a nonce or hash.
  • Check for `eval()` or `new Function()` — some AI-generated code uses dynamic code execution, which requires 'unsafe-eval' in CSP.
  • Review WebSocket connections — if your app uses real-time features, you may need connect-src entries.
  • Test in a staging environment — a broken CSP can break your entire app in production.
  • Step-by-step fix

    1. Add CSP via `next.config.js`

    The simplest approach for a Next.js app is to set the Content-Security-Policy header in the headers config. This method works for both App Router and Pages Router.

    // next.config.js
    const csp = `
      default-src 'self';
      script-src 'self' 'unsafe-inline' 'unsafe-eval';
      style-src 'self' 'unsafe-inline';
      img-src 'self' blob: data:;
      font-src 'self' https://fonts.gstatic.com;
      connect-src 'self' https://api.stripe.com wss://your-websocket-url.com;
      frame-src 'self' https://js.stripe.com;
      object-src 'none';
      base-uri 'self';
      form-action 'self';
    `;
    
    module.exports = {
      async headers() {
        return [
          {
            source: '/(.*)',
            headers: [
              {
                key: 'Content-Security-Policy',
                value: csp.replace(/\s{2,}/g, ' ').trim(),
              },
            ],
          },
        ];
      },
    };
    Note: 'unsafe-inline' and 'unsafe-eval' are broad permissions. For a vibe-coded app, they're often necessary because AI tools generate inline code. You can later tighten them by using nonces or hashes.

    2. Use middleware for dynamic policies (App Router)

    If your app needs per-route CSP (e.g., different policies for admin vs public pages), use middleware.

    // middleware.ts
    import { NextResponse } from 'next/server';
    import type { NextRequest } from 'next/server';
    
    export function middleware(request: NextRequest) {
      const nonce = Buffer.from(crypto.randomUUID()).toString('base64');
      const csp = `
        default-src 'self';
        script-src 'self' 'nonce-${nonce}' 'strict-dynamic';
        style-src 'self' 'nonce-${nonce}';
        img-src 'self' blob: data:;
        object-src 'none';
        base-uri 'self';
        form-action 'self';
      `;
      const requestHeaders = new Headers(request.headers);
      requestHeaders.set('x-nonce', nonce);
      requestHeaders.set('Content-Security-Policy', csp.replace(/\s{2,}/g, ' ').trim());
    
      return NextResponse.next({
        request: { headers: requestHeaders },
      });
    }

    Then apply the nonce to your <script> and <style> tags in layout or page files:

    // In your layout.tsx or page
    import { headers } from 'next/headers';
    
    export default function Page() {
      const nonce = headers().get('x-nonce') || '';
      return (
        <html>
          <head>
            <script nonce={nonce} src="/analytics.js" />
          </head>
          <body>{/* ... */}</body>
        </html>
      );
    }

    3. Add CSP as a meta tag (fallback)

    If you cannot modify server headers (e.g., static export), add a <meta> tag in <head>:

    <meta
      httpEquiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self' 'unsafe-inline'; ..."
    />

    Meta tag CSP is less secure (cannot use frame-ancestors or sandbox) but better than nothing.

    4. Test and monitor

    After deployment, use a security headers checker to confirm your CSP is present. Monitor browser console for violations — they appear as errors like:

    Refused to load the script 'https://example.com/script.js' because it violates the following Content Security Policy directive...

    Adjust your policy based on these reports. You can also set report-uri or report-to to collect violations.

    Common mistakes

    Mistake 1: Copy-pasting a generic CSP without testing

    AI-generated apps often include unexpected third-party scripts. A generic CSP like default-src 'self' will block everything, breaking your app. Always test in a staging environment first.

    Mistake 2: Using `'unsafe-inline'` everywhere

    Vibe-coded apps rely heavily on inline scripts and styles. While 'unsafe-inline' is a quick fix, it weakens security. For production, migrate to nonces or hashes. Tools like OverMCP's continuous security monitoring can alert you when your CSP drifts.

    Mistake 3: Forgetting WebSocket and API endpoints

    AI-built apps often use WebSockets for real-time features or connect to external APIs. If your CSP's connect-src doesn't include these, connections will fail silently.

    Mistake 4: Not handling `eval()` in AI-generated code

    Cursor, Bolt.new, and other AI tools sometimes generate code that uses eval() or new Function(). Without 'unsafe-eval', those parts of your app will break. Audit your code for dynamic execution before tightening CSP.

    Mistake 5: Setting CSP only in production

    Test CSP during development. Add it to your local next.config.js or middleware so you catch violations early.

    Why vibe-coded apps are especially vulnerable

    AI coding tools prioritize speed over security. They:

  • Generate inline event handlers like onclick="doSomething()" which require 'unsafe-inline'.
  • Inject external scripts for analytics, chatbots, or payment forms without notifying you.
  • Use dynamic imports or lazy loading that can trigger CSP violations.
  • Hardcode API keys in client-side code (use a secret leak scanner to catch those).
  • Without CSP, an XSS vulnerability in your vibe-coded app can execute arbitrary scripts — stealing cookies, redirecting users, or defacing your site. CSP is your second line of defense after input validation.

    FAQ

    What is a CSP header and why is it important?

    A Content Security Policy (CSP) header tells the browser which sources are allowed to load scripts, styles, images, and other resources. It prevents XSS attacks by blocking malicious content, even if an attacker injects a script tag.

    How do I know if my Next.js app is missing CSP headers?

    Use a free AI app security scanner or a security headers checker. If the Content-Security-Policy header is absent from your HTTP response, your app has no CSP protection.

    Can I use CSP with Vercel deployments?

    Yes. Vercel supports custom headers via next.config.js or vercel.json. For Vercel-specific guidance, try the Vercel security scanner to audit your deployment.

    Final thoughts

    Fixing missing CSP headers in a vibe-coded Next.js app doesn't have to be hard. Start with a permissive policy that matches your app's current behavior, then gradually tighten it as you clean up AI-generated code. Run a scan with OverMCP's free scanner to catch other common vulnerabilities like exposed secrets or missing SSL. Your users — and your future self — will thank you.

    Is your app secure?

    Free scan in 30 seconds. No signup needed.

    Scan My App Free