The most powerful header that almost nobody configures correctly
Content-Security-Policy (CSP) is, without question, the most powerful HTTP security header available. It acts as a whitelist that tells the browser exactly which resources are allowed to load and execute on your page: scripts, styles, images, fonts, iframes, and network connections.
When configured correctly, CSP neutralizes the vast majority of Cross-Site Scripting (XSS) attacks. When misconfigured, or absent entirely, your site remains exposed to one of the most commonly exploited web vulnerabilities. According to multiple security studies, over 90% of websites either have no CSP at all or implement it with errors that negate its protection.
XSS: the attack CSP prevents
Cross-Site Scripting (XSS) is an attack in which an attacker manages to inject malicious JavaScript into your web page. The mechanism is simple but devastating:
- The attacker injects a
<script>tag or an inline event handler (onclick,onerror) into your page, whether through a form field, a URL parameter, or content stored in a database. - The user's browser cannot distinguish legitimate scripts from injected ones: it executes them all equally.
- The malicious script can steal session cookies, redirect users to phishing sites, capture keystrokes, or modify the visible content of the page.
There are three main variants: reflected XSS (the payload travels in the URL), stored XSS (the payload is saved on the server and served to every visitor), and DOM-based XSS (the payload executes by manipulating the client-side DOM). CSP mitigates all three.
The solution CSP provides is conceptually elegant: it tells the browser "only execute scripts that come from these specific origins." If an attacker injects a <script> pointing to an unauthorized domain, the browser blocks it before execution.
CSP directives explained
CSP is composed of directives, each controlling a type of resource. These are the essential ones:
default-src-- The fallback directive. If you do not define a specific directive for a resource type, this one applies. It is the foundation of any solid policy.script-src-- Defines where scripts can be loaded from. This is the most critical directive because it controls JavaScript execution.style-src-- Controls the origin of CSS stylesheets.img-src-- Defines where images can be loaded from.connect-src-- Restricts the destinations forfetch(),XMLHttpRequest, WebSocket, and EventSource.font-src-- Controls where fonts can be loaded from.frame-src/frame-ancestors--frame-srccontrols which origins your page can load in iframes.frame-ancestorscontrols which pages can load yours in an iframe (replaces X-Frame-Options).report-uri/report-to-- Defines where violation reports are sent. Essential for monitoring and debugging.upgrade-insecure-requests-- Instructs the browser to automatically upgrade all HTTP requests to HTTPS.
The 5 most common CSP mistakes
Mistake 1: unsafe-inline in script-src
Content-Security-Policy: script-src 'self' 'unsafe-inline'
This single directive disables most of CSP's XSS protection. If you allow inline scripts, an attacker who injects <script>alert(document.cookie)</script> will succeed. If you need inline scripts, use nonces or hashes instead:
Content-Security-Policy: script-src 'self' 'nonce-a8F3kL9mPq'
Only scripts with the attribute nonce="a8F3kL9mPq" will execute -- a value that changes with every server response and that the attacker cannot predict.
Mistake 2: unsafe-eval
Content-Security-Policy: script-src 'self' 'unsafe-eval'
This allows eval(), new Function(), setTimeout('string'), and other forms of dynamic code execution. Many developers add it to make certain frameworks or libraries stop throwing console errors, but it opens a massive security hole. There is always a safer alternative.
Mistake 3: Wildcard sources
Content-Security-Policy: script-src *
A wildcard in script-src is practically equivalent to having no CSP at all. An attacker can host a malicious script on any domain in the world and your browser will execute it. The same applies to overly broad patterns like *.example.com: any compromised subdomain becomes an attack vector.
Mistake 4: Missing default-src
Without a default-src directive, any resource type that does not have its own explicit directive is left completely unrestricted. This means an attacker could exploit resource types you forgot to cover (for example, WebSockets via connect-src or fonts via font-src).
Mistake 5: Report-Only forever
Content-Security-Policy-Report-Only: default-src 'self'
Report-Only is excellent for the testing phase: it logs violations without blocking them. The problem is that many sites deploy it and never graduate to actual enforcement mode. Months or years later, the header is still in "report only" mode and provides zero effective protection.
Building a CSP from scratch
The correct approach is iterative. Here are the steps:
Step 1: Deploy in Report-Only mode
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
Step 2: Monitor violation reports for 1-2 weeks. Identify which legitimate resources are being flagged as violations.
Step 3: Add legitimate sources one by one. If you use Google Analytics, add https://www.google-analytics.com to script-src. If you load fonts from Google Fonts, add https://fonts.googleapis.com to style-src and https://fonts.gstatic.com to font-src.
Step 4: Replace unsafe-inline with nonces. Generate a cryptographically unique nonce per response and include it in every <script> tag and in the CSP directive.
Step 5: Switch to enforcement mode. Change Content-Security-Policy-Report-Only to Content-Security-Policy.
Step 6: Keep monitoring. Maintain report-uri active even in enforcement mode to detect regressions.
Bad CSP vs. good CSP
Bad (no real protection):
Content-Security-Policy: default-src *; script-src * 'unsafe-inline' 'unsafe-eval'
Good (solid protection):
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-rAnd0mV4lue'; style-src 'self' 'nonce-rAnd0mV4lue'; img-src 'self' data: https:; font-src 'self'; connect-src 'self' https://api.example.com; frame-ancestors 'none'; upgrade-insecure-requests; report-uri /csp-report
CSP Level 3 features
The latest version of the specification introduces significant improvements:
strict-dynamic-- Allows scripts already authorized (via nonce or hash) to load additional scripts without needing to list every domain. This dramatically simplifies policy maintenance.- Nonce-based approach -- Recommended by Google as the most robust way to implement CSP. A unique nonce per response replaces the need to enumerate origins.
trusted-types-- Prevents DOM-based XSS by enforcing that all values assigned to dangerous "sinks" (such asinnerHTML) pass through previously registered sanitization functions.
What UareSafe evaluates
UareSafe automatically analyzes your site's Content-Security-Policy header as part of the Technical Security dimension. Specifically, it verifies:
- Presence of the CSP header (not just Report-Only)
- Absence of
unsafe-inlineandunsafe-evalinscript-src - Existence of a
default-srcdirective - Detection of overly permissive wildcards
- Report-Only mode without active enforcement
These checks are part of the 8 security header controls that UareSafe evaluates within the Technical Security dimension (30% of the total certification score).
Conclusion
CSP is not optional in 2026. It is the single most effective defense against Cross-Site Scripting, the most prevalent web attack of the last two decades. A properly configured CSP policy protects your users from malicious scripts, data exfiltration, and session hijacking.
The process is clear: start in Report-Only mode, monitor, iterate, and switch to enforcement. You do not need a perfect policy on day one, but you do need to start. Your users deserve a secure browsing experience, and your site deserves protection that matches the threats of today.