Home Features For Vibe Coders For Developers Pricing Documentation Services Contact
← Back to blog

How to actually test your Content Security Policy

A Content Security Policy is meant to stop XSS and a class of injection attacks. Most CSPs in production are ornamental.

I’ve audited a few. Here’s how I figure out whether yours actually works, and how to fix it if it doesn’t.

Step 1: get the actual policy

curl -sI https://yoursite.com | grep -i content-security-policy

Read it. Out loud, if you have to. CSPs are unreadable by default, one giant string of directives. If you see any of these, you have a problem:

  • 'unsafe-inline' in script-src. This disables CSP’s main protection.
  • 'unsafe-eval' in script-src. Same thing, slightly more specific.
  • * as a source. Allows anything. Why bother.
  • https: with no host. Same as *, basically.

A real CSP has nonces or hashes, named hosts, and no wildcards. It’s verbose. That’s the point.

Step 2: test that it actually blocks things

Open your site. Open DevTools console. Try to inject a script:

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

If your console logs a CSP violation, the policy works. If the script loads, your policy is too permissive. Investigate.

Try inline injection too:

document.body.innerHTML += '<img src=x onerror="alert(1)">';

If alert fires, you have an XSS hole and your CSP isn’t catching it.

Step 3: check report-uri

A CSP without report-uri or report-to is a CSP that fails silently in production. You should be collecting violation reports. Tools like Report URI or even a simple Cloudflare Worker endpoint will work. Without this, you have no idea when an attack is being blocked, or when your own legitimate code is being blocked.

The first time I added a report endpoint to a site, I got 200 reports in the first day from extensions injecting scripts. After filtering those, I had 12 reports of actual issues, one of which was a third-party widget I’d forgotten to whitelist.

Step 4: the most common problems

In order of how often I see them:

Inline event handlers. <button onclick="..."> violates almost any reasonable CSP. The fix is to use addEventListener and tag your scripts with the nonce.

Inline styles. style-src 'self' will block <div style="...">. You either need 'unsafe-inline' for styles (acceptable, mostly) or to refactor inline styles into CSS classes (better).

Webpack/Vite chunk loading from a CDN. If your build output expects scripts to come from cdn.example.com but your CSP only lists 'self', every chunk fails to load.

Third-party widgets. Stripe, Cloudflare Turnstile, Google Tag Manager, Sentry, they all need explicit allowances. Read the docs, don’t guess.

Step 5: tighten over time

If you can’t ship a strict CSP all at once, ship a permissive one in report-only mode first:

Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'nonce-{NONCE}'; report-uri /csp-report

The browser enforces nothing, but reports violations. After two weeks, you’ll have a list of every violation. Fix them. Then flip to enforcement.

What “good enough” looks like

For a typical SaaS in 2026, a working CSP looks roughly like:

default-src 'self';
script-src 'self' 'nonce-{NONCE}' https://js.stripe.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https://*.cloudfront.net;
connect-src 'self' https://api.example.com;
frame-src https://js.stripe.com;
report-uri /csp-report;

Verbose. Specific. No wildcards in script-src. No 'unsafe-eval' anywhere.

The CSP you have probably looks nothing like this. Fix it.

Join the feed

Get feature releases and engineering updates delivered to your inbox.