How to Write a Content-Security-Policy (CSP)

How to Write a Content-Security-Policy (CSP)

A Content-Security-Policy (CSP) is an allowlist that tells the browser where a page is allowed to load resources from. Done right, a CSP is one of the strongest defenses against cross-site scripting (XSS), because the browser refuses to run scripts that are not on your list. Here is how to write one without breaking your site.

What a CSP actually does

Think of a CSP as a set of rules, one per resource type. Each rule names the origins that are trusted for that type. If an attacker injects a <script> pointing at their own server, the browser checks your policy, sees the origin is not allowed, and blocks it. No allowlist match, no execution.

You deliver a CSP as a response header:

Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'self'

You can also set it through a <meta http-equiv="Content-Security-Policy"> tag, though the header is preferred because it covers more directives.

The directives that matter

A few directives carry most of the weight:

  • default-src: the fallback for any resource type you do not name explicitly. Start with 'self'.
  • script-src: where scripts may load from. This is the heart of XSS protection.
  • style-src: where stylesheets may load from.
  • img-src: allowed image sources.
  • connect-src: origins for fetch, XHR, and WebSocket calls.
  • frame-ancestors: who may embed your page in an iframe. Set 'none' or 'self' to stop clickjacking.
  • base-uri: restrict the <base> tag so an attacker cannot rewrite relative URLs. Use 'self'.
  • object-src: set 'none' to kill legacy plugin vectors like Flash and applets.

Source keywords and inline scripts

Inside each directive you use source keywords:

  • ‘self’: the page’s own origin.
  • ‘none’: nothing is allowed, full stop.
  • ‘unsafe-inline’: allows inline <script> and onclick handlers. Avoid this. It reopens the exact XSS hole a CSP is meant to close.

If you have inline scripts you cannot remove, do not reach for 'unsafe-inline'. Use a nonce or a hash instead. A nonce is a random value you generate per request and add to both the header and the tag:

Content-Security-Policy: script-src 'self' 'nonce-r4nd0m123'
<script nonce="r4nd0m123">/* trusted inline code */</script>

A hash works similarly: you compute the SHA-256 of the script body and list it as 'sha256-...'. Either way, only the inline code you explicitly bless will run.

Build and test your CSP

A policy that is too strict will silently block your own scripts, styles, and fonts, so never deploy blind. Test first.

  1. Open the CSP Builder and pick the directives you need.
  2. Set each source to 'self' plus any trusted CDNs, and lock down object-src 'none', base-uri 'self', and frame-ancestors 'none'.
  3. Copy the policy and ship it as a Content-Security-Policy-Report-Only header first. This logs violations to the console without blocking anything.
  4. Browse your site, watch for reported violations, and adjust the allowlist in the CSP Builder until the report is clean.
  5. Swap the header name to Content-Security-Policy to enforce it.

Report-Only mode is the difference between a smooth rollout and a broken page in production.

Start strict, test in Report-Only, then enforce, and XSS loses its favorite door.

← All posts