Content Security Policy (CSP) is a browser security mechanism that tells the browser which places a page is allowed to load and run resources from — scripts, styles, images, fonts, frames, and network connections. You deliver the policy through the Content-Security-Policy HTTP header (or a <meta> tag). It is a set of directives separated by semicolons; each directive has a name and a list of allowed sources. CSP is first of all a layer of defense against cross-site scripting (XSS, where an attacker injects foreign code into a page) and, through the frame-ancestors directive, against clickjacking.
How does Content Security Policy work?
The server adds a Content-Security-Policy header to the HTTP response. The browser reads the policy and, on every attempt to fetch a resource or run code, checks whether the source is on the allowed list. If it is not, the browser blocks the resource and (optionally) sends a violation report. An enabled policy also blocks, by default, inline scripts embedded in the page (<script>...</script>, onclick attributes, javascript: URLs) and dynamic code execution (eval(), Function(), string-argument setTimeout/setInterval) unless you explicitly allow them.
The browser enforces the policy on the user’s side, but the server decides what it contains. It matters that the header is sent on every HTML response, not just the homepage.
How is a CSP policy delivered?
Content-Security-Policyheader — the recommended method, with every feature available (includingframe-ancestorsand reporting).Content-Security-Policy-Report-Onlyheader — the policy blocks nothing but reports violations. Used for testing before deployment.<meta http-equiv="Content-Security-Policy">tag — only some features work (noframe-ancestors, no reporting); sometimes handy for apps rendered in the browser.
It is better to avoid the deprecated X-Content-Security-Policy and X-Webkit-CSP headers.
What are the main CSP directives?
Directives fall into several groups. The most common are fetch directives, which restrict the sources of specific resource types:
default-src— the default policy and fallback for the other fetch directives.script-src— allowed JavaScript sources (the most important one for XSS defense).style-src— stylesheet sources.img-src,font-src,media-src— images, fonts, audio/video.connect-src— connections:fetch(),XMLHttpRequest, WebSocket, EventSource.object-src— plugins (<object>,<embed>); the recommended value is'none'.frame-src,worker-src,manifest-src— frames, workers, the app manifest.
The other groups are document directives (base-uri, sandbox), navigation directives (form-action, frame-ancestors), reporting directives (report-to, report-uri), and a transport-security directive (upgrade-insecure-requests).
What are the source-list keywords?
'self'— resources from the same origin (domain).'none'— block the resource type entirely.https:,data:— an allowed scheme (e.g. anything over HTTPS, or data URIs).- a hostname or wildcard, e.g.
*.example.com. 'unsafe-inline'— allows scripts and styles embedded in the page (weakens protection, not recommended).'unsafe-eval'— allowseval()and related APIs (not recommended).'nonce-...'— a one-time random token that lets through a specific inline script.'sha256-...'/'sha384-...'/'sha512-...'— a hash that lets through a script or style with exactly that content.'strict-dynamic'— carries the trust given to a script via a nonce or hash over to scripts that script itself loads; ignores host lists and'unsafe-inline'.
What is a strict CSP based on nonces and hashes?
Host allowlists are hard to maintain and are often bypassable (e.g. through open redirects or hosted libraries vulnerable to JSONP). The modern, recommended practice is a strict CSP based on nonces or hashes:
- Nonce — on each response the server generates a random token, places it in the header (
script-src 'nonce-RANDOM') and in thenonceattribute of trusted<script>tags. The token must differ on every response and be unpredictable. It requires dynamic template rendering. - Hash — the policy contains a Base64-encoded SHA-256/384/512 digest of the script content. The browser hashes the content and loads it only on a match. Good for static content; any change, even a space, invalidates the hash.
'strict-dynamic'— lets a script trusted via a nonce or hash load further scripts without marking each one; this eases integrating third-party libraries.
A typical strict policy combines these with restrictions: script-src 'nonce-RANDOM' 'strict-dynamic'; object-src 'none'; base-uri 'none'.
How does CSP protect against XSS and clickjacking?
With XSS, CSP works as a second layer of defense: even if an attacker manages to inject code, a strict policy will not let it run, because the injected script has no valid nonce and matches no hash. This does not replace checking input data and safely encoding output data on the server — CSP is defense in depth, not a sole safeguard.
With clickjacking, the frame-ancestors directive decides who may embed the page in a frame ('none', 'self', or specific domains). It supersedes the older X-Frame-Options header. The base-uri 'none' directive blocks <base> tag abuse, and upgrade-insecure-requests forces resources to HTTPS (a complement to, not a replacement for, the HSTS header).
How does CSP violation reporting work?
CSP can report every violation, so it can become a source of security telemetry useful to SOC teams and for application monitoring.
- Reporting API (current) — report endpoints are defined by the
Reporting-Endpointsheader, and the policy points to a group viareport-to group-name. The report is JSON withtype: "csp-violation"and details (blockedURL,effectiveDirective,documentURL,disposition, etc.). report-uri(deprecated) — POSTs a report to a given URL (Content-Type: application/csp-report). For compatibility with older browsers, declare both directives: newer ones usereport-to, older ones fall back toreport-uri.
From a detection standpoint, the CSP report stream lets you spot early signs of script-injection attempts, loading of resources from unauthorized domains (e.g. data exfiltration through an injected connect-src target), and misconfigurations. The Content-Security-Policy-Report-Only mode lets you watch such events without blocking traffic — handy when tuning a policy and judging the impact of a new version.
How do you roll out CSP without breaking the app?
- Run the policy in
Content-Security-Policy-Report-Onlymode and collect violation reports. - Clean up inline code: move scripts to separate files, replace event-handler attributes (
onclick) withaddEventListener. - Add nonces or hashes to trusted scripts; for third-party libraries, consider
'strict-dynamic'. - Lock the policy down with
object-src 'none',base-uri 'none', andframe-ancestors. - Switch to the enforced
Content-Security-Policyheader while still watching the reports.
Learn more
- Cross-Site Request Forgery (CSRF) — CSP can reduce CSRF impact by limiting where a page can load scripts from.
- OWASP Top 10 — CSP helps mitigate several OWASP Top 10 web risks, especially XSS.
- Web Shell Attacks — CSP can block script injection paths that may help plant a web shell.