What is a Content Security Policy (CSP)?
A Content Security Policy (CSP) is an added layer of HTTP header security that helps detect and mitigate Cross-Site Scripting (XSS) and data injection attacks. It works by strictly whitelisting the approved domains and origins that a browser is permitted to load resources from.
1Understanding CSP Architecture
Beyond the basic definition, CSP provides a standard method for website owners to declare a rigorous defense-in-depth security posture. By specifying a whitelist of permitted domains, a CSP essentially neutralizes XSS payloads, even if a vulnerability exists in the underlying application code.
2How to Use the Visual CSP Builder
Writing CSP headers manually is prone to syntax errors that can completely break modern web applications. The Visual CSP Header Builder solves this by providing a deterministic, GUI-driven construction environment.
| Step | Action | Description |
|---|---|---|
| 1. Select Preset | Choose a Baseline | Start with a Strict, Balanced, or Dev preset to establish the foundational policy footprint. |
| 2. Configure Directives | Toggle Core Sources | Navigate through default-src, script-src, and style-src in the left sidebar to explicitly whitelist your CDNs and APIs. |
| 3. Inject Custom Hosts | Add Third-Parties | Use the Custom Hosts input to authorize specific domains (e.g., https://api.stripe.com) or cryptographic hashes (e.g., 'sha256-...'). |
| 4. Live Validation | Check the Grade | Monitor the real-time security grading engine. If you accidentally enable 'unsafe-inline', the engine will immediately flag the critical vulnerability. |
3Anatomy of CSP Directives
A CSP is composed of one or more directives, separated by semicolons. Each directive governs a specific type of resource fetching or execution context within the browser document.
The most critical fetch directive is script-src, which restricts where JavaScript can be loaded from and how it can be executed. Other vital directives include style-src (CSS), img-src (images), and connect-src (XHR/Fetch/WebSockets).
4Default-Src vs. Specific Directives
The default-src directive serves as a fallback for the majority of fetch directives. If a specific directive (like font-src) is completely omitted from the policy, the browser will enforce the rules defined in default-src for that resource type.
Crucial exception: default-src does not act as a fallback for all directives. Directives like frame-ancestors, report-uri, and sandbox are entirely unaffected by default-src and will default to allowing everything if omitted.
default-src 'none'; as the foundation of your policy, and then explicitly open up specific directives as needed. This "default deny" approach is the cornerstone of secure CSP design.
5The Dangers of 'unsafe-inline' & 'unsafe-eval'
Many developers, frustrated by CSP breaking their legacy applications, resort to adding 'unsafe-inline' to their script-src. This is a critical security failure.
'unsafe-inline' allows the execution of inline <script> blocks and inline event handlers (like onclick). Since almost all XSS vulnerabilities rely on injecting inline scripts, authorizing 'unsafe-inline' effectively neutralizes the primary protection CSP offers.
Similarly, 'unsafe-eval' permits the use of eval() and related string-to-code APIs. While less dangerous than inline scripts, it still provides an execution vector for attackers who can manipulate string inputs.
6Implementing Nonce-Based Strict CSP
To secure inline scripts without using 'unsafe-inline', the modern web relies on Nonces (Number Used Once). A nonce is a cryptographically strong, base64-encoded random string generated dynamically on the server for every single page load.
The server injects this nonce into the CSP header:
Content-Security-Policy: script-src 'nonce-rAnd0m123' 'strict-dynamic';
And matches it on the authorized inline scripts:
<script nonce="rAnd0m123">...</script>
Because an attacker injecting a malicious script cannot predict the randomly generated nonce for that specific HTTP response, the browser will refuse to execute the injected payload.
7Hash-Based CSP for Static SPAs
Single Page Applications (SPAs) deployed via static CDNs (like Vercel or Netlify) often cannot generate dynamic nonces on a per-request basis. In these scenarios, Hashes provide the alternative.
By computing the SHA-256 (or SHA-384/512) hash of the exact inline script content, developers can authorize that specific code block within the header:
Content-Security-Policy: script-src 'sha256-B2yPHKaXnvFWtRChIbabYmUBFZdVfKKXHbWtWidDVF8=';
If an attacker injects a script, its hash will inevitably differ, and the browser will block it. This ensures pristine integrity for statically compiled frontend assets.
8CSP Reporting: report-uri vs report-to
Deploying a strict CSP on a legacy application can cause massive breakage. To mitigate this, CSP offers a Report-Only mode via the Content-Security-Policy-Report-Only header. In this mode, the browser will not block resources; it will only simulate the blocks and send JSON violation reports to a specified endpoint.
| Directive | Status | Description |
|---|---|---|
report-uri |
Deprecated | The legacy method of specifying an endpoint URL. Still widely supported by older browsers. |
report-to |
Modern | The modern Reporting API. Requires a separate Report-To HTTP header to define the endpoint group. Provides out-of-band, batched telemetry. |
9Defeating Clickjacking with frame-ancestors
Clickjacking occurs when an attacker embeds your site within a transparent <iframe> on a malicious domain, tricking users into clicking buttons they didn't intend to. The legacy defense was the X-Frame-Options header.
The CSP frame-ancestors directive supersedes X-Frame-Options. It allows you to explicitly declare exactly which parent origins are allowed to embed your application.
Content-Security-Policy: frame-ancestors 'self' https://partner.com;
If you do not want your site embedded anywhere, ever, use frame-ancestors 'none';.
10HTTP Header vs. HTML Meta Tag CSP
While CSP is primarily delivered via HTTP response headers, it can also be delivered directly within the HTML document using a `` tag. This is useful for static sites where developers lack access to the web server configuration.
<meta http-equiv="Content-Security-Policy" content="default-src 'self';">
Limitations: Meta tag CSPs cannot use reporting directives (report-uri, report-to), framing directives (frame-ancestors), or the sandbox directive. For complete security, HTTP headers are strictly required.
11Technical Glossary
Source List
The space-separated string of authorized domains, keywords (like 'self'), or hashes that follow a directive.
'strict-dynamic'
A keyword that trusts scripts loaded by an already-trusted (nonce-authorized) script, simplifying dependency management.
XSS (Cross-Site Scripting)
A vulnerability where an attacker injects executable code into a web page viewed by other users.
Mixed Content
Occurs when an initial HTML document is loaded over a secure HTTPS connection, but other resources are loaded over insecure HTTP.
Data URI (data:)
A scheme that allows creators to embed small files inline in documents, commonly used for base64 encoded images.
CSP Evaluator
Tools (like this one) that parse and syntactically validate the security posture of a generated policy string.
12CSP for WordPress: A Complete Guide
WordPress is the most widely targeted CMS for XSS attacks due to its plugin ecosystem. A correctly configured CSP is the single most impactful server-hardening step you can take for any WordPress site.
eval() via jQuery — making a strict CSP extremely difficult. The correct approach is a nonce-based CSP injected via functions.php.
The best practice is to generate a per-request nonce in functions.php and use the wp_script_attributes filter to inject it into all enqueued scripts automatically:
add_filter('wp_script_attributes', function($attr) {
if (isset($attr['id'])) $attr['nonce'] = get_nonce_for_request();
return $attr;
});Then inject the CSP header via the send_headers action hook, including your generated nonce:
add_action('send_headers', function() {
$nonce = get_nonce_for_request();
header("Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{$nonce}' 'strict-dynamic'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; object-src 'none'; frame-ancestors 'self';");
});| Common WordPress Plugin | Required CSP Exception | Risk Level |
|---|---|---|
| Google Analytics (GA4) | script-src https://www.googletagmanager.com; connect-src https://www.google-analytics.com | Low |
| Google reCAPTCHA | script-src https://www.google.com https://www.gstatic.com; frame-src https://www.google.com | Low |
| Stripe Payments | script-src https://js.stripe.com; frame-src https://js.stripe.com | Low |
| Elementor Page Builder | style-src 'unsafe-inline' (unavoidable without patching) | Medium |
| WooCommerce + Inline JS | Nonce-based approach required; can't avoid inline scripts | Medium |
13CSP for Next.js, React & SPAs
Single Page Applications built with Next.js or Create React App inject significant amounts of inline script, including runtime chunk bootstrapping. The recommended approach differs significantly depending on whether you use the Next.js App Router or the older Pages Router.
| Approach | Mechanism | Best For |
|---|---|---|
| App Router (Next.js 13+) | Use next.config.js headers() with a dynamic nonce via middleware.ts | SSR / SSG hybrid apps |
| Pages Router | Override _document.tsx to inject nonce into <Head> and pass via getInitialProps | Legacy Next.js apps |
Static Export (next export) | Use SHA-256 hashes for all inline scripts. Generate hashes at build time. | Static CDN deployments |
| Vite / CRA | Use the vite-plugin-csp or manual hash generation as a post-build step | SPA without SSR |
middleware.ts:import { NextResponse } from 'next/server';
export function middleware(request) {
const nonce = Buffer.from(crypto.randomUUID()).toString('base64');
const csp = `default-src 'self'; script-src 'self' 'nonce-${nonce}' 'strict-dynamic'; style-src 'self' 'nonce-${nonce}'; object-src 'none';`;
const response = NextResponse.next();
response.headers.set('Content-Security-Policy', csp);
return response;
}14Common CSP Bypass Techniques & Mistakes
Even a well-intentioned CSP can be completely bypassed if common mistakes are made. The following techniques are used by attackers to circumvent poorly configured policies — and are exactly what the Attack Surface Analyzer tool above detects.
| Bypass Technique | Trigger Condition | Remediation |
|---|---|---|
| JSONP Endpoint Abuse | Any whitelisted domain that hosts a JSONP endpoint (e.g., googleapis.com) can be used to inject arbitrary JS | Prefer nonce/hash over domain whitelisting; use 'strict-dynamic' |
| Angular Template Injection | Whitelisting https://ajax.googleapis.com to load AngularJS 1.x enables CSP bypass via template injection | Never whitelist known JSONP/Angular CDN hosts; load from self |
| Open Redirect Abuse | A whitelisted domain that has an open redirect can be used to pivot to attacker domains | Audit all whitelisted domains for open redirect vulnerabilities |
Missing base-uri | Without base-uri 'self', an attacker can inject <base href="..."> to hijack relative URL loading | Always add base-uri 'self'; or base-uri 'none'; |
Missing object-src | Browsers will allow Flash and plugin content, which can execute arbitrary code | Always include object-src 'none'; |
data: in script-src | Allows execution of <script src="data:text/javascript,alert(1)"> | Never add data: to script-src |
15Complete CSP Directive Reference (All 25)
The full CSP Level 3 specification defines 25 distinct directives across four categories. This is the most comprehensive reference for developers needing to understand every possible configuration option.
| Directive | Category | Purpose | Fallback To |
|---|---|---|---|
default-src | Fetch | Fallback for all fetch directives not explicitly specified | — |
script-src | Fetch | Controls JavaScript sources and execution | default-src |
script-src-elem | Fetch | Controls <script> elements specifically | script-src |
script-src-attr | Fetch | Controls inline event handlers like onclick | script-src |
style-src | Fetch | Controls CSS sources | default-src |
style-src-elem | Fetch | Controls <style> and <link rel=stylesheet> | style-src |
style-src-attr | Fetch | Controls inline style attributes | style-src |
img-src | Fetch | Controls image and favicon sources | default-src |
font-src | Fetch | Controls font sources via @font-face | default-src |
connect-src | Fetch | Restricts fetch, XHR, WebSocket, and EventSource URLs | default-src |
media-src | Fetch | Controls audio and video sources | default-src |
object-src | Fetch | Controls Flash and plugin content | default-src |
frame-src | Fetch | Controls valid sources for <iframe> | child-src |
child-src | Fetch | Controls workers and embedded frames | default-src |
worker-src | Fetch | Controls Worker/SharedWorker/ServiceWorker scripts | child-src |
manifest-src | Fetch | Controls Web App Manifest sources | default-src |
prefetch-src | Fetch | Controls prefetch and prerender targets | default-src |
frame-ancestors | Navigation | Restricts which parents can embed this page (replaces X-Frame-Options) | — |
form-action | Navigation | Restricts form submission target URLs | — |
navigate-to | Navigation | Restricts URLs that the document may navigate to | — |
base-uri | Document | Restricts <base> element URLs | — |
sandbox | Document | Applies restrictions similar to iframe sandbox attribute | — |
upgrade-insecure-requests | Other | Rewrites HTTP URLs to HTTPS before fetching | — |
block-all-mixed-content | Other | Blocks all HTTP resources on HTTPS pages (deprecated) | — |
report-to / report-uri | Reporting | Specifies endpoint for violation reports | — |
16Nginx & Apache: Production Configuration Patterns
The correct syntax for deploying a CSP header differs between Nginx and Apache. Both must be configured at the server or virtual host level for the header to apply to all responses, including static assets.
| Server | Config File | Syntax |
|---|---|---|
| Nginx | nginx.conf or /etc/nginx/sites-available/*.conf | add_header Content-Security-Policy "default-src 'self';" always; |
| Apache 2.4+ | .htaccess or httpd.conf | Header always set Content-Security-Policy "default-src 'self';" |
| Apache (mod_header) | Requires mod_headers enabled | a2enmod headers; systemctl restart apache2 |
| Cloudflare Workers | Worker script | response.headers.set('Content-Security-Policy', '...') |
| Vercel | vercel.json | { "headers": [{ "key": "Content-Security-Policy", "value": "..." }] } |
always parameter in add_header directives. Without it, the CSP header will not be sent on error responses (4xx, 5xx), leaving those pages unprotected.
add_header Content-Security-Policy-Report-Only "default-src 'self'; report-uri /csp-violations" always;Deploy this first for 1–2 weeks, monitor the violation reports, then switch to enforcing mode once all issues are resolved.
17CSP Browser Support Matrix (2026)
CSP Level 3 is supported by all modern browsers as of 2026. However, some newer directives and keywords have varying support levels that developers must account for, especially when supporting older enterprise environments running Internet Explorer 11.
| Feature | Chrome | Firefox | Safari | Edge | IE 11 |
|---|---|---|---|---|---|
| CSP Level 1 (basic) | ✓ Full | ✓ Full | ✓ Full | ✓ Full | ✓ Partial |
| CSP Level 2 (nonce, hash) | ✓ v40+ | ✓ v35+ | ✓ v10+ | ✓ v15+ | ✗ None |
'strict-dynamic' | ✓ v52+ | ✓ v52+ | ✓ v15.4+ | ✓ v79+ | ✗ None |
frame-ancestors | ✓ v40+ | ✓ v33+ | ✓ v10+ | ✓ v15+ | ✗ None |
script-src-elem | ✓ v75+ | ✓ v105+ | ✓ v16+ | ✓ v79+ | ✗ None |
report-to (new API) | ✓ v70+ | ⚠ Partial | ⚠ Partial | ✓ v79+ | ✗ None |
'wasm-unsafe-eval' | ✓ v97+ | ✓ v102+ | ✓ v16+ | ✓ v97+ | ✗ None |
IE 11 Compatibility: If you must support IE 11, include both X-Content-Security-Policy (Firefox 3.x/IE10 prefixed header) alongside the standard CSP header. However, IE 11 will only enforce a very limited subset.
18CSP vs. Other HTTP Security Headers
CSP is a powerful tool but works best as part of a complete HTTP security header stack. Here is how CSP relates to and complements (or supersedes) other security headers:
| Header | Protects Against | Relationship to CSP | Still Needed? |
|---|---|---|---|
X-Frame-Options | Clickjacking | Superseded by CSP frame-ancestors; CSP is more flexible | Legacy fallback only |
X-Content-Type-Options: nosniff | MIME-sniffing attacks | Complementary — prevents browser from misinterpreting file types | Yes, always add |
Strict-Transport-Security (HSTS) | SSL stripping, man-in-the-middle | Complementary — CSP's upgrade-insecure-requests is related but not a substitute | Yes, always add |
Permissions-Policy | Feature abuse (camera, mic, geolocation) | Complementary — controls browser API access, not resource loading | Yes, recommended |
Referrer-Policy | Information leakage via Referer header | Complementary — controls what URL info is shared with third parties | Yes, recommended |
X-XSS-Protection | Reflected XSS (IE/Chrome legacy) | Deprecated — CSP is the modern replacement and is far more comprehensive | No — disable it |
Content-Security-Policy, Strict-Transport-Security, X-Content-Type-Options, Referrer-Policy, and Permissions-Policy. This combination achieves an A+ grade on security header scanners like securityheaders.com.