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>andonclickhandlers. 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.
- Open the CSP Builder and pick the directives you need.
- Set each source to
'self'plus any trusted CDNs, and lock downobject-src 'none',base-uri 'self', andframe-ancestors 'none'. - Copy the policy and ship it as a Content-Security-Policy-Report-Only header first. This logs violations to the console without blocking anything.
- Browse your site, watch for reported violations, and adjust the allowlist in the CSP Builder until the report is clean.
- Swap the header name to
Content-Security-Policyto enforce it.
Report-Only mode is the difference between a smooth rollout and a broken page in production.
Related tools
- Setting the whole header bundle? Use the Security Headers Generator.
- Tuning cross-origin access? Try the CORS Header Validator.
- Checking what a live site already sends? Run the HTTP Header Analyzer.
Start strict, test in Report-Only, then enforce, and XSS loses its favorite door.