What is a Content Security Policy (CSP)? How CSP works and how to deploy it
A Content Security Policy (CSP) is an HTTP security header that tells a browser which resources a page is allowed to load. Learn how CSP works, what directives are available, and how to deploy it without breaking your site.
On this page
- How CSP works
- Key directives and keywords
- Clickjacking and HTTPS upgrades
- Strict CSP: nonces and hashes
- Testing and monitoring your policy
- FAQ
- What does a Content Security Policy protect against?
- What are the most important CSP directives to know?
- Is a CSP hard to deploy on an existing site?
- What does 'unsafe-inline' do and why should it be avoided?
- Summary
The browser's same-origin policy keeps code from https://mybank.com isolated from https://evil.example.com — in theory. In practice, cross-site scripting (XSS) attacks bypass this by tricking a site into serving malicious code alongside its legitimate content. The browser has no way to distinguish injected script from the real thing, so it runs everything. A Content Security Policy (CSP) is the web's primary defence against this class of attack: it is an HTTP response header that acts as an explicit allowlist, telling the browser exactly which scripts, styles, images and other resources are permitted to run on a given page. This guide explains how CSP works, what each directive does, and how to roll one out without taking your site offline.
How CSP works
A CSP is delivered to the browser as the Content-Security-Policy HTTP response header, set by your web server or CDN on every response — not just the HTML document. The policy is written as a series of directives separated by semicolons, each controlling a different category of resource:
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-cdn.com; object-src 'none'
Each directive has a name and a value. The directives together form an allowlist: the browser loads only the resources that match one of the values you specified, and blocks everything else. If an attacker manages to inject a <script src="https://evil.example.com/xss.js"> tag, the browser refuses to load it because https://evil.example.com is not in the script-src allowlist.
The default-src directive acts as a fallback for any resource type that does not have its own more specific directive. In the example above, images, fonts and frames inherit from default-src 'self', while scripts are additionally allowed from https://trusted-cdn.com. Any plugin content (<object>, <embed>) is blocked outright by object-src 'none'.
You can also deliver a CSP via a <meta http-equiv> tag inside <head>. This is useful for purely static, client-side applications where you do not have server-level control of headers. Note, however, that certain directives — frame-ancestors, the report-uri/report-to reporting mechanism, and sandbox — only take effect when delivered as a real HTTP response header.
Key directives and keywords
CSP defines several categories of directives. The most important ones are:
Fetch directives control which resources can be loaded. The main ones are:
default-src— fallback for unspecified resource typesscript-src— where JavaScript is allowed to load from (the most critical directive for XSS protection)style-src— where CSS stylesheets may be loaded fromimg-src— where images may be loaded fromfont-src— where web fonts may be loaded fromobject-src— where plugins such as<object>,<embed>,<applet>may be loaded from; almost always set to'none'frame-src— where<iframe>and<frame>elements are allowed to load from
Document directives include base-uri, which restricts what can appear in the <base> element's href attribute (preventing base-tag injection attacks).
Navigation directives include form-action, controlling which pages a <form> may submit to.
Reporting directives control violation reporting (see the Testing section below).
The values a directive accepts include:
'self'— resources from the same origin as the document'none'— block this resource type entirely'unsafe-inline'— allow inline<script>blocks and event handlers; this weakens XSS protection significantly and should be avoided where possible'unsafe-eval'— alloweval()and similar dynamic code execution; almost never needed'strict-dynamic'— allows a trusted script to load its own dependencies, propagating trust through the script's own loading chain rather than through a static allowlist'nonce-NUMBER'— a per-request random token; only scripts with a matchingnonceattribute are allowed'sha256-HASH'— a SHA-256, SHA-384 or SHA-512 hash of the exact script content; only scripts whose content produces that hash are allowed- Host patterns such as
https://cdn.example.comor*.example.org
Clickjacking and HTTPS upgrades
CSP does more than block XSS. The frame-ancestors directive controls which sites are allowed to embed your page in an <iframe>. Setting frame-ancestors 'none' prevents your site from being framed entirely, which defends against clickjacking — where an attacker overlays your page with a transparent iframe to trick users into clicking the attacker's buttons instead of yours. frame-ancestors is a more flexible replacement for the older X-Frame-Options header.
The upgrade-insecure-requests directive tells the browser to rewrite any HTTP resource requests on the page to HTTPS before making them. This is useful when migrating a site from HTTP to HTTPS: include it in the policy and any mixed-content request (an image loaded over HTTP, a script loaded over HTTP) is automatically upgraded rather than blocked.
Strict CSP: nonces and hashes
A CSP that relies on 'unsafe-inline' offers weak XSS protection, because an attacker who can inject an attribute like onclick= can bypass it. A strict CSP avoids 'unsafe-inline' entirely and instead uses one of two mechanisms for allowing legitimate inline scripts:
Nonces are a random token generated server-side for each HTTP response and included both in the CSP header and in the nonce attribute of the <script> tags you intend to run:
Content-Security-Policy: script-src 'nonce-416d1177-4d12-4e3b-b7c9-f6c409789fb8'
<script nonce="416d1177-4d12-4e3b-b7c9-f6c409789fb8">
// this script is allowed to run
</script>
The nonce changes on every request, so an attacker cannot predict it. Any script injected without the correct nonce is blocked.
Hashes are a permanent, deterministic alternative: you compute the SHA-256 (or SHA-384 or SHA-512) hash of the exact script content, then add it to the policy:
Content-Security-Policy: script-src 'sha256-cd9827ad...='
The hash never changes, so this works well for static sites. You can combine 'strict-dynamic' with either nonces or hashes: the trusted root scripts are verified by the nonce or hash, and those scripts are then permitted to load their own dependencies without the nonce/hash requirement propagating to every consumer.
Testing and monitoring your policy
Deploying a restrictive CSP on a live site without testing first risks breaking your own functionality. Two tools help you do this safely.
Content-Security-Policy-Report-Only sends the browser a header that applies the policy in monitoring mode — violations are reported but nothing is blocked. This lets you see what your policy would catch without the risk of taking your site offline:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-reports
Violation reports are sent as an HTTP POST to an endpoint you control. Modern browsers use the Reporting API, where you first declare an endpoint:
Reporting-Endpoints: csp-endpoint="https://example.com/csp-reports"
Content-Security-Policy: default-src 'self'; report-to csp-endpoint
The browser sends a JSON body identifying the blocked resource, the directive that blocked it, and the page that triggered it. Review these reports before enforcing the policy to find any legitimate resources your allowlist is missing.
FAQ
What does a Content Security Policy protect against?
CSP primarily defends against cross-site scripting (XSS) attacks by controlling which scripts a page is allowed to execute. It also helps protect against clickjacking (via frame-ancestors), man-in-the-middle attacks on resources (via upgrade-insecure-requests), and injection of malicious iframes or plugins (via object-src). CSP is a defence-in-depth measure — the first line of defence against content injection remains input validation and output encoding on the server side.
What are the most important CSP directives to know?
script-src is the most critical for XSS protection, because it controls where JavaScript may be loaded from. default-src sets the baseline for all resource types, so a conservative default-src 'self' combined with explicit overrides for each resource type you actually use is a good starting point. object-src 'none' should almost always be set, since plugin content (Flash is long dead, but PDFs and Java applets still exist) is a high-risk attack surface. frame-ancestors 'none' prevents your site from being embedded in iframes on other domains.
Is a CSP hard to deploy on an existing site?
It can be, if the site uses a lot of inline scripts, third-party widgets or CDN-hosted resources without a strict allowlist. The safest rollout path is: (1) enable Content-Security-Policy-Report-Only with a permissive policy to collect baseline violation reports; (2) add one or two directives per week while monitoring reports; (3) once reports are clean, switch from Report-Only to Content-Security-Policy to enforce the policy. Using a nonce or hash-based strict CSP is the most secure approach, but it requires server-side generation of per-request nonces, which is harder to implement than a static hash-based policy.
What does 'unsafe-inline' do and why should it be avoided?
'unsafe-inline' tells the browser to allow any <script> block written directly in the HTML and any event handler attribute such as onclick= or onerror=. This makes CSP easy to deploy on existing sites because it does not require changing how scripts are included. However, it also means that an XSS payload written as an inline script or an event handler is not blocked — an attacker who can inject <script>alert(1)</script> or <img onerror=alert(1)> will have their code execute. Strict CSP avoids 'unsafe-inline' by using nonces or hashes to whitelist only specific, intentional inline scripts.
Summary
A Content Security Policy is an HTTP response header that acts as an explicit resource allowlist for the browser. By specifying which domains scripts, styles, images and other resource types may be loaded from, CSP blocks content-injection attacks — particularly XSS — even if an attacker manages to inject malicious markup into your page. The key directives to understand are script-src (for JavaScript), object-src 'none' (to block plugins), and frame-ancestors 'none' (to prevent clickjacking). A strict CSP uses per-request nonces or pre-computed hashes instead of 'unsafe-inline', which gives the strongest XSS protection. Deploy incrementally using Content-Security-Policy-Report-Only before switching to enforcement mode. CSP is not a substitute for input validation, but it is one of the most effective browser-native defences against the most common class of web attacks.
Sources
- MDN — Content Security Policy (developer.mozilla.org/en-US/docs/Web/HTTP/CSP
- CC BY-SA 2.5+)
- web.dev — Content Security Policy (web.dev/articles/csp
- Apache 2.0)
- W3C — CSP Level 3 Working Draft (w3.org/TR/2026/WD-CSP3-20260729)
Share this guide
Related guides
- What is authentication? Passwords, biometrics, SSO, and modern methods explained
Authentication is the process of verifying who you are. Learn how passwords, multi-factor authentication, biometrics, SSO, OAuth, and password managers work — and which methods offer the best protection for your application.
- What is CSRF (Cross-Site Request Forgery)? How it works and how to prevent it
Cross-Site Request Forgery (CSRF) tricks a logged-in browser into sending unwanted requests to a trusted site. This guide explains how the attack works and the defenses that stop it.
- What is SQL injection? How it works and how to prevent it
SQL injection is one of the most common web vulnerabilities — this guide explains how it works, the main attack types, and the secure coding practices that prevent it.