VibeSecurity

Fix

Fix: Content-Security-Policy header missing

A Content Security Policy (CSP) tells the browser which scripts, styles, frames and connections your pages may use. Without one, any script that gets injected into your page runs with full access to it.

By the VibeSecurity team2 min read

What this means

The finding means your server sends no Content-Security-Policy header. Your site is not broken or breached because of that. A CSP is a second line of defence: if a cross-site scripting bug lets an attacker inject a script, a good policy stops the browser from running it or from sending data to the attacker's server. It can also control who may frame your pages.

Why it happens

  • No framework ships a CSP by default, because a correct policy depends on the third-party services you use.
  • AI tools generate features, not response headers, unless asked.
  • A policy was tried once, broke analytics or payments, and was removed rather than adjusted.
  • The header is set in a meta tag on some pages only, or on the app but not on the marketing site.

How to fix it

  1. 1List what your pages load: your own files, analytics, fonts, payment and auth providers, your API and your database URL (for example your Supabase project).
  2. 2Write a policy starting from default-src 'self' and add each source to the right directive.
  3. 3Deploy it as Content-Security-Policy-Report-Only. Nothing is blocked in this mode. Violations appear in the browser console.
  4. 4Use the app fully, note every reported violation and either allow the source or remove the thing loading it.
  5. 5For frameworks that inject inline scripts, such as Next.js, use per-request nonces as the Next.js guide shows, instead of 'unsafe-inline'.
  6. 6Rename the header to Content-Security-Policy to enforce it, and keep watching the console after each release.
next.config.js: starter policy in report-only mode
const csp = [
  "default-src 'self'",
  "script-src 'self'",
  "style-src 'self' 'unsafe-inline'",
  "img-src 'self' data: https:",
  "font-src 'self'",
  "connect-src 'self' https://YOUR-PROJECT.supabase.co",
  "frame-ancestors 'none'",
  "base-uri 'self'",
  "form-action 'self'",
  "object-src 'none'",
].join('; ');

module.exports = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [{ key: 'Content-Security-Policy-Report-Only', value: csp }],
      },
    ];
  },
};

How to confirm the fix

  • Check the header is present on your live pages with the curl command below.
  • Load your site with the console open. In report-only mode, fix every reported violation before enforcing.
  • After enforcing, run the snippet below in the console on your own site. The browser should refuse to load the script.
Check the header, then test a block on your own site
curl -s -D - -o /dev/null https://www.yourapp.com | grep -i content-security-policy

const s = document.createElement('script');
s.src = 'https://example.com/not-allowed.js';
document.head.appendChild(s);

Frequently asked questions

Will a CSP break my app?

An enforcing policy that misses a source will block it. That is why you start in report-only mode, which blocks nothing.

Does React or Next.js make a CSP unnecessary?

No. React escapes text, which prevents most injection, but raw HTML rendering, third-party scripts and unsafe links remain. A CSP limits the damage when one of those goes wrong.

Can I set the policy with a meta tag?

Yes for most directives, but frame-ancestors and report-only mode do not work in a meta tag. A response header is the better place.

Why is 'unsafe-inline' allowed for styles in the example?

Many UI libraries inject inline styles, and the risk from injected styles is lower than from injected scripts. Tighten it later with nonces if your stack allows.

Sources

  1. 1.MDN: Content Security Policy (CSP)
  2. 2.MDN: Content-Security-Policy header
  3. 3.MDN: Content-Security-Policy-Report-Only
  4. 4.OWASP Content Security Policy Cheat Sheet
  5. 5.Next.js: Content Security Policy