Security Headers

Content-Security-Policy for people who do not write CSPs

The one-sentence version

A Content-Security-Policy is a doorman for your page. You give the browser a list of sources you trust — your own domain, a specific CDN, an analytics provider — and the browser refuses to load scripts, styles or frames from anywhere else. If an attacker manages to inject a <script> that points somewhere not on your list, it simply does not run. That is the whole value: it neutralizes the most common cross-site-scripting payloads by default.

Why XSS is the thing it stops

Cross-site scripting is when an attacker gets their JavaScript to execute in your users' browsers — to steal sessions, read data, or act as the user. Most other defenses try to stop the injection from happening. CSP takes the opposite angle: it assumes something might slip through and makes the injected script useless, because it is not from an allowed source. Defense-in-depth, and it is the layer that saves you on a bad day.

The trap: unsafe-inline

Here is the part that catches almost everyone. Inline scripts — JavaScript written directly in the HTML rather than loaded from a file — are convenient, and frameworks lean on them. To allow them, people add unsafe-inline to their policy. But CSP cannot tell your inline script from an attacker's injected inline script, so unsafe-inline permits both. A policy with unsafe-inline in script-src looks like a CSP and provides little of the protection. It is the single most common real-world CSP finding for exactly this reason.

How to roll one out without breaking everything

A real CSP can block legitimate scripts if you guess wrong, so do it in stages:

  1. Start in report-only mode. Use Content-Security-Policy-Report-Only with a reporting endpoint. The policy is not enforced — you just collect what would have been blocked. This shows you everything your site actually loads.
  2. Build the allowlist from reality. Add the legitimate sources the reports reveal, rather than guessing up front.
  3. Replace inline scripts with nonces or hashes. Instead of unsafe-inline, give each legitimate inline script a one-time nonce the policy trusts, so attacker-injected inline scripts still fail.
  4. Switch to enforcing and drop unsafe-inline. Once report-only is quiet, enforce the policy for real.

A reasonable starting point

A solid policy is restrictive by default and opens up only what you need: lock default-src to your own origin, allow scripts only from yourself and your nonces, and add the specific third parties you actually use. Tight first, loosened deliberately — never the other way around.

Check what your CSP really does

A CSP can look protective and not be, which is why it is worth verifying rather than assuming. An automated scan reads your policy and flags the weakening pieces like unsafe-inline, plus a missing CSP entirely. Scan your site and see whether your policy actually holds.

Related reading

FAQ

Why is unsafe-inline considered bad if my inline scripts are fine?
Because the browser cannot distinguish your inline script from an attacker's injected one, so unsafe-inline trusts both. It re-opens the exact gap CSP is meant to close. Use nonces or hashes to allow your own inline scripts specifically.
Will adding a CSP break my website?
It can if it is too strict, which is why you start in report-only mode. That collects violations without blocking anything, so you can see what your site really loads and build an accurate allowlist before enforcing.
Is a CSP still needed if I sanitize user input?
Yes — they are layers. Input sanitization tries to prevent injection; CSP limits the damage if something gets through. Strong sites do both, because neither is perfect on its own.