ToolHub
查看所有文章

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:

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">
Important: The meta tag cannot be used for report-only mode. If you need to test your policy before enforcement, you must use the HTTP header approach. Additionally, the meta tag only supports one CSP policy, while the HTTP header can deliver multiple policies.

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 Generator

Frequently 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.