Content Security Policy is the most powerful security header you can deploy. A well-written CSP stops entire classes of attacks — XSS, data injection, clickjacking — before they reach your users. This guide covers everything from the basics to production-ready implementation.
CSP is an HTTP response header that tells the browser exactly which resources it is allowed to load and execute on your page. If a resource is not explicitly whitelisted, the browser refuses to load it — even if an attacker manages to inject a script tag into your HTML.
Before CSP existed, there was no way for a server to tell the browser "only run scripts from my own domain." Attackers who found an XSS vulnerability could inject any script they liked, and the browser would faithfully execute it. CSP changes that by moving the trust decision to the server.
CSP is delivered as an HTTP response header named Content-Security-Policy. It contains a list of directives separated by semicolons, each controlling a different type of resource.
Cross-Site Scripting (XSS) happens when an attacker manages to inject JavaScript into a page that other users view. The most common path is through user-supplied content — a comment, a search query, a username — that gets rendered in HTML without proper sanitisation.
Once an attacker's script runs in a victim's browser, it has full access to the page's DOM, cookies, and local storage. It can steal session tokens, redirect the user to a phishing page, or silently send form data to an attacker-controlled server. Without CSP, the browser has no way to distinguish the attacker's injected script from your own legitimate code.
With a strong CSP in place, injected scripts are blocked before they execute because they don't come from an approved source. The attack is neutralised at the browser level, independent of whether your server-side sanitisation is perfect.
A CSP header is built from directives. Each directive controls a specific content type. Here are the most important ones:
| Directive | Controls | Example value |
|---|---|---|
default-src | Fallback for all types not explicitly listed | 'self' |
script-src | JavaScript files and inline scripts | 'self' 'nonce-abc123' |
style-src | CSS files and inline styles | 'self' 'unsafe-inline' |
img-src | Images, including from data: URIs | 'self' data: https://cdn.example.com |
object-src | Plugins: Flash, Java applets | 'none' |
base-uri | What URLs <base> can use | 'self' |
form-action | Where forms can submit data | 'self' |
frame-ancestors | Which pages can embed this page in an iframe | 'none' |
connect-src | XHR, fetch, WebSocket, EventSource | 'self' https://api.example.com |
font-src | Web font files | 'self' https://fonts.gstatic.com |
If a directive is not listed, the browser falls back to default-src. If default-src is also missing, the browser applies no restriction for that content type.
Two CSP keywords destroy most of the protection CSP provides:
'unsafe-inline' allows all inline scripts including injected ones. 'unsafe-eval' allows eval(), Function(), and setTimeout with string arguments — all common XSS payloads.
A CSP with script-src 'self' 'unsafe-inline' provides almost no XSS protection because the attacker's injected <script> tag counts as "inline." Many developers add 'unsafe-inline' because they have inline event handlers (onclick="...") or inline <script> blocks they don't want to refactor. The correct solution is nonces.
A nonce (number-used-once) is a random value the server generates on every request. It's included in the CSP header and on each legitimate inline script tag. The browser only runs inline scripts that carry the matching nonce — injected scripts don't have it, so they're blocked.
Content-Security-Policy: script-src 'self' 'nonce-r4nd0mV4lue8x'
In your HTML, legitimate inline scripts get the matching nonce attribute:
<script nonce="r4nd0mV4lue8x"> // this runs — it has the correct nonce initApp(); </script>
The nonce must be cryptographically random and change with every page load. If an attacker could predict the nonce, the protection would fail. Most web frameworks provide built-in nonce generation.
CSP can send violation reports to a server endpoint whenever the browser blocks something. This is invaluable for discovering policy mistakes before they affect users:
Content-Security-Policy: default-src 'self'; report-uri /csp-reports
Start with Content-Security-Policy-Report-Only — this logs violations without actually blocking anything, letting you test a new policy in production before enforcing it.
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{SERVER_NONCE}';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.jsdelivr.net 'nonce-{SERVER_NONCE}';
style-src 'self' https://fonts.googleapis.com;
img-src 'self' data: https://images.example.com;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com;
object-src 'none';
frame-ancestors 'none';
script-src * or script-src https: — these allow loading scripts from any HTTPS site, negating XSS protection.object-src 'none', old Flash-based XSS techniques still work even with a strict default-src.base-uri 'self', an attacker who can inject a <base> tag can redirect all relative URLs to their server.http:// source in a CSP is a downgrade risk — use https:// everywhere.connect-src or they'll be blocked.Use this as a starting point, then expand based on what resources your app actually needs:
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'nonce-REPLACE_WITH_SERVER_NONCE'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; report-uri /csp-reports
Once you're confident in the policy, change the header name to Content-Security-Policy to start blocking violations instead of only reporting them.
WebAudit checks your CSP quality, detects unsafe-inline and unsafe-eval, and gives you a score with specific fix recommendations.
Check your site's security now →