Content Security Policy (CSP) Guide: Protect Your Website
Cross-site scripting (XSS) remains one of the most prevalent web vulnerabilities, consistently ranking in the OWASP Top 10. Despite decades of awareness, XSS attacks continue to compromise websites because traditional defenses like input validation and output encoding are not foolproof. Content Security Policy (CSP) provides a powerful additional layer of defense by telling browsers exactly which resources are allowed to load and execute on your pages. When properly configured, CSP can completely neutralize XSS attacks by preventing the execution of unauthorized scripts. This guide covers everything you need to know about CSP, from understanding the directives to building and deploying a production-ready policy.
What Is Content Security Policy?
Content Security Policy is an HTTP response header (or meta tag) that allows website operators to declare approved sources of content that the browser may load. It operates as a whitelist: any resource not explicitly allowed by the policy is blocked. This mechanism prevents attackers from injecting malicious scripts, styles, or other resources into your pages, even if they find a way to insert HTML into your application.
Without CSP, the browser trusts every resource referenced in a
webpage. If an attacker manages to inject a
<script> tag pointing to their malicious server,
the browser happily downloads and executes it. CSP changes this
default behavior by requiring that all resource sources be explicitly
whitelisted by the page author.
How CSP Prevents XSS
XSS attacks work by tricking the browser into executing scripts that the website author did not intend. There are three main types of XSS, and CSP addresses each differently:
Stored XSS
An attacker stores malicious script in the server (for example, in a comment or profile field). When other users view the page, the script executes in their browser. CSP prevents this by blocking inline scripts and restricting script sources to trusted origins. Even if the attacker's script is stored in the database, the browser refuses to execute it because it does not match the allowed sources.
Reflected XSS
The malicious script is embedded in a URL and reflected back in the server's response. When a victim clicks the crafted link, the script executes. CSP blocks reflected XSS the same way it blocks stored XSS: by prohibiting inline scripts and restricting script sources.
DOM-Based XSS
The attack occurs entirely on the client side, where malicious data
flows from a source (like the URL) to a sink (like
innerHTML) without server involvement. CSP is less
effective against DOM-based XSS because the vulnerability exists in
JavaScript code that is already authorized. However, a strict CSP that
prohibits unsafe-eval and unsafe-inline can
still prevent certain DOM-based attacks that rely on
eval(), Function(), or inline event
handlers.
CSP Directives Reference
CSP policies are composed of directives, each controlling a specific type of resource. Understanding each directive is essential for building an effective policy.
Core Directives
| Directive | Controls | Example |
|---|---|---|
default-src
|
Fallback for all resource types |
default-src 'self'
|
script-src
|
JavaScript sources |
script-src 'self' cdn.example.com
|
style-src
|
CSS sources |
style-src 'self' 'unsafe-inline'
|
img-src
|
Image sources |
img-src 'self' data: https:
|
font-src
|
Font sources |
font-src 'self' fonts.googleapis.com
|
connect-src
|
AJAX, WebSocket, fetch sources |
connect-src 'self' api.example.com
|
media-src
|
Audio and video sources |
media-src 'self'
|
object-src
|
Plugin sources (Flash, Java) |
object-src 'none'
|
frame-src
|
iframe sources |
frame-src 'self'
|
Navigation and Document Directives
| Directive | Controls | Example |
|---|---|---|
base-uri
|
Allowed URLs for <base> element
|
base-uri 'self'
|
form-action
|
Allowed form submission targets |
form-action 'self'
|
frame-ancestors
|
Who can embed this page (anti-clickjacking) |
frame-ancestors 'none'
|
navigate-to
|
Allowed navigation targets |
navigate-to 'self'
|
Reporting Directives
| Directive | Controls | Example |
|---|---|---|
report-uri
|
URL to send violation reports |
report-uri /csp-report
|
report-to
|
Reporting group name (newer API) |
report-to csp-endpoint
|
Source Values Reference
Each directive accepts one or more source values that define what is permitted. Understanding these values is critical for writing correct policies.
| Source Value | Meaning |
|---|---|
'self'
|
Same origin as the page (scheme, host, port must match) |
'none'
|
Nothing is allowed (most restrictive) |
'unsafe-inline'
|
Allows inline scripts/styles and event handlers (weakens XSS protection) |
'unsafe-eval'
|
Allows eval(), Function(), and similar (weakens XSS protection) |
'strict-dynamic'
|
Trusts scripts that have a valid nonce or hash, and scripts they load |
'nonce-{value}'
|
Allows scripts/styles with a matching nonce attribute |
'sha256-{hash}'
|
Allows scripts/styles whose content matches the hash |
https:
|
Any resource loaded over HTTPS |
data:
|
Resources embedded as data URIs |
*.example.com
|
Any subdomain of example.com |
Building a CSP: Step by Step
Creating an effective CSP requires a methodical approach. Rushing to enforcement without proper testing will break your website. Follow these steps to build a policy safely.
Step 1: Start with Report-Only Mode
Always begin with Content-Security-Policy-Report-Only.
This header tells the browser to log violations without blocking
anything, so you can observe what resources your page needs without
risking breakage.
Content-Security-Policy-Report-Only:
default-src 'none';
script-src 'self';
style-src 'self';
img-src 'self';
font-src 'self';
connect-src 'self';
report-uri /csp-report
Step 2: Collect and Analyze Violation Reports
Set up an endpoint to receive violation reports. Each report contains the violated directive, the blocked URI, and the document that triggered the violation. Monitor these reports over a sufficient period (at least a few days for production traffic) to capture all legitimate resource loads, including those from third-party integrations, analytics, and widgets.
{
"csp-report": {
"document-uri": "https://example.com/page",
"violated-directive": "script-src 'self'",
"blocked-uri": "https://cdn.analytics.com/tracker.js",
"line-number": 42
}
}
Step 3: Add Legitimate Sources
For each legitimate violation, add the appropriate source to the
corresponding directive. Be as specific as possible: prefer exact
hostnames over wildcards, and avoid https: which allows
any HTTPS origin.
Content-Security-Policy-Report-Only:
default-src 'none';
script-src 'self' cdn.analytics.com cdn.example.com;
style-src 'self' fonts.googleapis.com;
img-src 'self' data: cdn.example.com;
font-src 'self' fonts.gstatic.com;
connect-src 'self' api.example.com;
frame-src www.youtube-nocookie.com;
report-uri /csp-report
Step 4: Switch to Enforcement
Once no legitimate violations appear in your reports, switch from
Content-Security-Policy-Report-Only to
Content-Security-Policy. Keep reporting enabled so you
can catch any issues that were missed during testing.
Strict CSP with Nonces
The most secure CSP configuration uses nonces (numbers used once) to authorize individual script elements. Each page load generates a unique random nonce, which is included in the CSP header and added as an attribute to your script tags. The browser only executes scripts whose nonce matches the header value. This approach completely blocks inline scripts and injected scripts because attackers cannot predict the nonce value.
// Server generates a random nonce per request
const nonce = crypto.randomUUID();
// CSP header includes the nonce
Content-Security-Policy:
default-src 'none';
script-src 'nonce-{random-nonce}' 'strict-dynamic';
style-src 'self';
img-src 'self';
font-src 'self';
connect-src 'self';
// HTML includes the same nonce on script tags
<script nonce="{random-nonce}">
// This script is allowed because its nonce matches
console.log('Hello from authorized script');
</script>
The 'strict-dynamic' source value extends trust to
scripts loaded by authorized scripts. This means if your main
application script (with a valid nonce) dynamically loads additional
scripts, those are automatically trusted without needing to list every
source in the CSP header. This dramatically simplifies policy
management for applications that use dynamic script loading.
Common CSP Mistakes
Even experienced developers make mistakes when implementing CSP. Here are the most common pitfalls and how to avoid them:
Using 'unsafe-inline'
Adding 'unsafe-inline' to
script-src effectively negates CSP's XSS protection. It
allows all inline scripts, event handlers (onclick,
onerror, etc.), and javascript: URIs. If
your application requires inline scripts, use nonces or hashes
instead. For CSS, 'unsafe-inline' is less dangerous but
should still be avoided when possible by moving styles to external
stylesheets.
Overly Broad Wildcards
Using *.com or https: as source values
allows any HTTPS origin, which significantly weakens your policy. An
attacker who finds an XSS vulnerability could load scripts from any
HTTPS server. Always specify exact hostnames or, at minimum, specific
subdomains.
Missing default-src
The default-src directive serves as a fallback for any
directive not explicitly set. Without it, unspecified resource types
are unrestricted. Always set default-src 'none' or
default-src 'self' as your baseline and then selectively
open up specific resource types.
Forgetting object-src
Plugins like Flash and Java applets can execute arbitrary code and
bypass CSP. Always set object-src 'none' unless you have
a specific need for plugins (which is extremely rare in modern web
development).
Not Testing Third-Party Resources
Third-party scripts from analytics, advertising, and social media
widgets are often the most challenging part of CSP configuration.
These scripts may load additional resources dynamically, use inline
styles, or evaluate code. Test thoroughly with report-only mode and
consider using 'strict-dynamic' to handle their dynamic
loading patterns.
CSP and Modern Frameworks
Modern JavaScript frameworks and build tools can generate CSP-friendly code, but they require proper configuration.
React, Vue, and Angular
These frameworks generally work well with CSP because they avoid
inline scripts by default. However, you may encounter issues with:
inline styles generated by component libraries (solved by using
style-src 'unsafe-inline' or nonce-based style
authorization), development mode hot reloading (which uses eval), and
server-side rendering hydration scripts (which may need nonces).
Configure your build tool to inject nonces into generated script tags.
Webpack and Vite
Build tools can be configured to output CSP-compatible code. Disable
eval in your bundler configuration, use nonce placeholders that your
server replaces with actual nonces, and ensure source maps are not
exposed in production (they can leak source code through the
script-src directive).
CSP Level 3 Features
CSP Level 3 introduced several important features that make strict policies more practical:
- 'strict-dynamic': Allows scripts loaded by trusted scripts to execute without listing their origin in the policy. This is the recommended approach for modern applications.
- Nonce-based policies: Per-request nonces are more secure than hashes because they cannot be reused across page loads and cannot be predicted by attackers.
- Hash-based policies: SHA-256, SHA-384, or SHA-512 hashes of script content. Useful for scripts that never change, but require updating the CSP header whenever the script content changes.
- 'unsafe-hashes': Allows event handlers to be authorized by hash. This is a compromise for legacy code that uses inline event handlers.
-
report-to directive: Uses the Reporting API instead
of the legacy
report-urifor more reliable violation reporting.
Deploying CSP
CSP can be delivered as an HTTP response header or as a
<meta> tag. The HTTP header is preferred because it
covers all resources on the page and cannot be modified by attackers.
The meta tag is useful for static sites or when you cannot modify
server headers.
HTTP Header (Recommended)
// Apache (.htaccess)
Header set Content-Security-Policy "default-src 'self'; script-src 'self' cdn.example.com"
// Nginx
add_header Content-Security-Policy "default-src 'self'; script-src 'self' cdn.example.com";
// Express.js (Node)
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' cdn.example.com"
);
next();
});
Meta Tag (Fallback)
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' cdn.example.com">
Need to create a Content Security Policy for your website? Use our free CSP Generator to build your policy with an easy interface, then test it in report-only mode before deploying.
CSP GeneratorFrequently Asked Questions
What is Content Security Policy (CSP)?
Content Security Policy is an HTTP response header that tells the browser which resources are allowed to load on a webpage. It prevents cross-site scripting (XSS), clickjacking, and other code injection attacks by restricting the sources of scripts, styles, images, and other resources. Without CSP, browsers load any resource referenced in the page, including malicious scripts injected by attackers.
What is the difference between report-only and enforce mode?
Content-Security-Policy-Report-Only sends violation reports without blocking any resources, making it safe for testing. Content-Security-Policy enforces the policy and blocks violations. Always start with report-only mode to identify all legitimate resources your page needs before switching to enforcement mode.
Does CSP replace input validation?
No. CSP is a defense-in-depth layer, not a replacement for input validation and output encoding. You should still sanitize all user input, escape output, and follow secure coding practices. CSP provides an additional safety net that limits the damage if an XSS vulnerability is discovered, but it should not be your only line of defense.
How do I test my CSP before enforcing it?
Use Content-Security-Policy-Report-Only to test your policy without breaking your site. Configure a report-uri endpoint to collect violation reports. Monitor the reports for a sufficient period (days or weeks) to identify all legitimate resources. Fix any violations by adding the necessary sources to your policy. Once no legitimate violations appear, switch to the enforcement header.
Can CSP break my website?
Yes, a misconfigured CSP can block legitimate resources, causing broken functionality, missing images, failed API calls, and non-working scripts. This is why you should always test with report-only mode first and gradually tighten your policy. Using 'unsafe-inline' or 'unsafe-eval' weakens protection but may be necessary for legacy applications.