Understanding Content Security Policy
Content Security Policy (CSP) is an HTTP security header that provides fine-grained control over which resources a browser is allowed to load and execute on a web page. By defining a whitelist of trusted content sources, CSP dramatically reduces the impact of cross-site scripting (XSS) attacks. Even if an attacker manages to inject malicious code into your page, the browser will refuse to execute it unless the code's source is explicitly permitted by your policy.
CSP was introduced as a W3C standard to address the fundamental problem of XSS: browsers cannot distinguish between scripts that are part of your application and scripts injected by an attacker. CSP solves this by giving developers a mechanism to explicitly declare which content sources are legitimate. The browser then enforces these declarations, blocking anything that violates the policy.
How CSP Prevents XSS
CSP prevents XSS through several mechanisms:
- Blocking inline scripts: By default, a CSP that defines a
script-srcdirective blocks all inline<script>tags and inline event handlers (likeonclick). Since most XSS attacks rely on injecting inline scripts, this alone eliminates a large percentage of XSS vectors. - Blocking eval(): CSP blocks
eval(),setTimeout(string),setInterval(string), andnew Function(string)by default, preventing code injection through JavaScript evaluation functions. - Source whitelisting: Only scripts loaded from explicitly approved origins are executed. An injected script tag pointing to an attacker's server is blocked because that server is not in the whitelist.
- Nonce and hash-based validation: Specific inline scripts can be allowed by assigning them a unique nonce or hash, ensuring only known scripts execute.
CSP Directive Syntax
A CSP header consists of one or more directives separated by semicolons. Each directive controls a specific type of resource:
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self' https://api.example.com; frame-ancestors 'none'
Key Directives
- default-src: The fallback directive for any resource type that does not have its own directive. Setting
default-src 'self'restricts all resources to the same origin unless overridden by a more specific directive. - script-src: Controls which scripts can execute. This is the most critical directive for XSS prevention. Values can include origins (
https://cdn.example.com),'self','nonce-{value}', or'sha256-{hash}'. - style-src: Controls CSS sources. Many frameworks inject inline styles, often requiring
'unsafe-inline'(which is less risky for styles than for scripts). - img-src: Controls image sources. You often need
data:for base64-encoded images andhttps:to allow images from any HTTPS origin. - font-src: Controls web font sources. Commonly includes Google Fonts or your CDN.
- connect-src: Controls origins for XMLHttpRequest, Fetch, WebSocket, and EventSource connections.
- frame-src: Controls which origins can be loaded in iframes.
- frame-ancestors: Controls which origins can embed your page in a frame. This replaces X-Frame-Options and protects against clickjacking.
- base-uri: Restricts the URLs that can be used in the
<base>element, preventing base tag injection attacks. - form-action: Restricts which URLs can be used as form submission targets.
- object-src: Controls Flash, Java, and other plugin content. Set to
'none'in most modern applications. - media-src: Controls audio and video sources.
- worker-src: Controls sources for web workers and service workers.
Source Values
Directives accept various source values:
'self'- The page's own origin (scheme + host + port).'none'- No sources allowed.https://example.com- A specific origin.https:- Any HTTPS origin.data:- Data URIs (base64-encoded content).'unsafe-inline'- Allows inline scripts/styles (weakens CSP significantly for scripts).'unsafe-eval'- Allows eval() and similar functions (weakens CSP significantly).'nonce-{base64}'- Allows specific elements with a matching nonce attribute.'sha256-{base64}'- Allows specific inline content matching the hash.'strict-dynamic'- Trusts scripts loaded by already-trusted scripts.
Nonce-Based Policies
Nonce-based CSP is the recommended approach for allowing inline scripts while maintaining strong XSS protection. A nonce (number used once) is a random, unique value generated by the server for each page load. The server includes the nonce in both the CSP header and the script tags that should be allowed:
# CSP Header
Content-Security-Policy: script-src 'nonce-4AEemGb0xJptoIGFP3Nd'
# In the HTML
<script nonce="4AEemGb0xJptoIGFP3Nd">
// This script executes because the nonce matches
console.log('Legitimate script');
</script>
<script>
// This script is BLOCKED because it has no nonce
alert('Injected by attacker');
</script>
The nonce must be cryptographically random and different for every page load. If the nonce is predictable or reused, an attacker could include it in their injected scripts. Modern server-side frameworks provide built-in support for generating and injecting nonces.
The 'strict-dynamic' source expression works with nonces to allow scripts loaded by trusted scripts. If a nonced script dynamically creates another script element, the child script is automatically trusted, even if its source is not in the whitelist. This simplifies policies for applications that use script loaders.
CSP Reporting
CSP provides two reporting mechanisms:
report-uri (Deprecated but Widely Supported)
Content-Security-Policy: default-src 'self'; report-uri /csp-violation-endpoint
When a violation occurs, the browser sends a JSON POST request to the specified endpoint with details about the violation, including the blocked URI, the violated directive, and the page where the violation occurred.
report-to (Modern Standard)
Content-Security-Policy: default-src 'self'; report-to csp-group
Report-To: {"group":"csp-group","max_age":86400,"endpoints":[{"url":"/csp-violation-endpoint"}]}
The report-to directive uses the Reporting API, which is the modern replacement for report-uri. For maximum compatibility, include both directives.
Report-Only Mode
The Content-Security-Policy-Report-Only header lets you test a policy without enforcing it. Violations are reported but not blocked. This is essential for deploying CSP without breaking your site:
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; report-uri /csp-violation-endpoint
Deploy in report-only mode first, collect and analyze violations, adjust the policy, and only switch to enforcement mode (Content-Security-Policy) when the policy no longer generates false positives.
CSP Deployment Strategy
- Audit your resources: Use browser developer tools to catalog every script, style, image, font, and connection your site makes. Note the origin of each resource.
- Draft a policy: Start with
default-src 'self'and add specific directives for each resource type based on your audit. - Deploy as report-only: Set the policy using
Content-Security-Policy-Report-Onlyand set up an endpoint or service to collect violation reports. - Analyze reports: Review violations over a period of days or weeks. Distinguish between legitimate resources that need to be allowed and actual attack attempts or unnecessary resources.
- Refine the policy: Adjust directives to eliminate false positives while maintaining the tightest possible restrictions.
- Enforce: Switch to the enforcing
Content-Security-Policyheader. - Monitor continuously: Keep the reporting endpoint active to detect new violations caused by site changes or new attack patterns.
Common CSP Challenges
- Third-party scripts: Analytics, advertising, chat widgets, and social media embeds load scripts from external domains, each requiring its own CSP entry. Some third-party scripts dynamically load additional scripts, requiring
'strict-dynamic'or broad domain whitelisting. - Inline scripts and styles: Legacy applications often use inline event handlers and style attributes. Refactoring to external files or nonce-based policies requires development effort.
- Unsafe-inline and unsafe-eval: Using these values significantly weakens CSP.
'unsafe-inline'for scripts effectively disables XSS protection. If you must use them, combine them with nonces or hashes for a partial safeguard. - Browser compatibility: CSP Level 3 features like
'strict-dynamic'are supported in all modern browsers but not in older ones. Test across your supported browser matrix.
CSP and Other Security Headers
CSP works best as part of a comprehensive security header strategy. HSTS ensures encrypted connections, X-Content-Type-Options prevents MIME confusion, and CSP controls resource loading. A WAF can inject or enforce CSP headers at the edge for sites that cannot easily modify their origin server configuration. See our guide on configuring security headers for server-specific implementation details.
Summary
Content Security Policy is the most powerful security header available for preventing cross-site scripting and other client-side injection attacks. By defining explicit whitelists for all content sources, using nonce-based policies for inline scripts, and leveraging report-only mode for safe deployment, CSP provides granular control over what runs in your users' browsers. While it requires careful planning and ongoing maintenance, the security benefits make CSP an essential part of any web application's defense strategy.
Need help securing your infrastructure? Explore NOC.org plans to get started.