Content Security Policy (CSP) for Beginners
Content Security Policy is the single strongest defence a website has against cross-site scripting. It works by giving the browser an explicit list of where scripts, styles and other resources may come from — so even if an attacker injects code into your page, the browser refuses to run it.
The short version
CSP is a Content-Security-Policy header made of directives like default-src and script-src. It is powerful but easy to break your own site with, so always start in Content-Security-Policy-Report-Only mode, watch what it would have blocked, then enforce. Check whether your site sends a policy with our headers check.
What CSP actually does
Cross-site scripting (XSS) happens when an attacker manages to inject their own JavaScript into a page your users trust. That script can steal sessions, rewrite the page or exfiltrate data. CSP flips the model from “run anything on the page” to “only run what the site owner has approved.” The browser enforces the rules, so it protects users even against injection paths you did not know existed.
How the header is built
A policy is a series of directives separated by semicolons. Each directive names a resource type and lists the sources allowed for it.
Content-Security-Policy: default-src 'self'; script-src 'self'; img-src 'self' data:; object-src 'none'; frame-ancestors 'self'
The most common directives:
| Directive | Controls |
|---|---|
default-src | The fallback for any resource type you do not list explicitly. |
script-src | Where JavaScript may load from. The most security-critical directive. |
style-src | Where CSS may load from. |
img-src | Where images may load from. |
frame-ancestors | Who may embed your page in a frame — the modern replacement for X-Frame-Options. |
object-src | Plugins such as Flash or Java. Set to 'none' on almost every site. |
The keyword 'self' means “the same origin as the page.” 'none' blocks everything. You can also list specific hostnames such as https://cdn.example.com.
Inline scripts, nonces and hashes
The hard part of CSP is that a strong policy forbids inline <script> blocks and onclick= handlers, because those are exactly how injected code runs. You have two safe ways to allow the specific inline scripts you actually wrote:
<!-- Nonce: a random value regenerated on every response -->
Content-Security-Policy: script-src 'self' 'nonce-r4nd0m123'
<script nonce="r4nd0m123">/* your code */</script>
A nonce is a one-time random token you put on both the header and each trusted script tag; the browser runs only matching tags. A hash such as 'sha256-...' lets you allow a specific script by its exact content. Avoid 'unsafe-inline' — it switches the protection off. The best long-term goal is a nonce-based policy so no unapproved script can execute.
Roll it out with report-only mode
Deploying a strict policy blind will almost certainly break something. Instead, ship it in report-only mode first:
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'nonce-...'; report-uri /csp-report
In this mode the browser reports what it would have blocked but does not actually block anything. You collect the violation reports, spot the legitimate resources you forgot to allow — an analytics script, a font host, an embedded video — and widen the policy until reports go quiet. Then switch the header name to Content-Security-Policy to start enforcing.
'unsafe-inline' or a wildcard * to script-src to make errors disappear defeats the entire point of CSP. If you need inline scripts, use a nonce or hash instead of opening the door to any script.Where CSP fits
CSP’s frame-ancestors directive also supersedes the older X-Frame-Options header for clickjacking protection in modern browsers. It is one of the highest-value entries on the full security headers checklist. Once your policy is live, run a scan to confirm it is being sent correctly.
Frequently asked questions
Will CSP break my third-party scripts?
It can, if you forget to list their hosts. That is exactly why you start in report-only mode: it surfaces every external source your pages actually use before you enforce anything.
Do I still need to sanitise user input?
Yes. CSP is a strong second line of defence, not a replacement for escaping output and validating input. Treat it as one layer among several.
What is the difference between a nonce and a hash?
A nonce is a fresh random token added to trusted script tags on each response; a hash allows a specific script by a fingerprint of its exact contents. Nonces are usually easier to maintain for dynamic pages.
Related guides
HTTP Security Headers: The Complete Checklist
Every important HTTP security header explained, what it protects against, and a copy-paste starting configuration.
Read →HTTP Security HeadersHSTS Explained: Strict-Transport-Security Done Right
How HSTS forces HTTPS, what the preload list means, and how to roll it out without locking yourself out.
Read →HTTP Security HeadersX-Frame-Options and Clickjacking Protection
How clickjacking attacks work and how X-Frame-Options and frame-ancestors stop your site being embedded maliciously.
Read →Check your site against this guide
Run a free ScanOpsPro scan and see how your site handles the fundamentals.
Run a free scan