Playbook
    security
    advanced

    Add or fix a Content-Security-Policy header

    Your site has no CSP header or a weak one. Here is a starter policy that blocks 90% of XSS without breaking your site.

    30 minutesLast reviewed 2026-06-09

    What is happening

    A Content-Security-Policy (CSP) header tells the browser which sources are allowed to load scripts, styles, images, and other content. Without it, any XSS bug becomes a full site takeover.

    Starter policy (report-only first)

    Add this header in report-only mode for a week so you can find violations without breaking anything:

    Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https:; frame-ancestors 'none'; report-uri /csp-report

    After a week of zero violations on real traffic, switch Content-Security-Policy-Report-Only → Content-Security-Policy to enforce.

    Strict policy (after audit)

    Once you have removed inline scripts and styles:

    Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{random}'; style-src 'self' 'nonce-{random}'; img-src 'self' data: https:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'

    Generate a fresh nonce per request on the server and pass it into every <script nonce="..."> tag.

    Where to set the header

  1. Nginx: add_header Content-Security-Policy "..." always; in the server block.
  2. Apache: Header set Content-Security-Policy "..." in .htaccess.
  3. Cloudflare Workers / Netlify / Vercel: add via _headers file or edge middleware.
  4. Express/Next.js: use the helmet package or a middleware that sets the header.
  5. Common gotchas

  6. `unsafe-inline` defeats the point. It is fine as a temporary bridge, but every week with it in place is a week of unmitigated XSS risk.
  7. Third-party scripts without explicit allowlist. GA, Stripe, Intercom, and similar need their domains added to script-src.
  8. `frame-ancestors 'none'` breaks embeds. If your site is meant to be embeddable, use frame-ancestors 'self' https://partner.com.
  9. What this doesn't do

    CSP does not protect against CSRF, click-jacking outside of frame-ancestors, or backend SSRF. Add SameSite cookies, CSRF tokens, and request validation separately.