Content Security Policy is a response header that instructs browsers to enforce a strict allowlist of resources your page is permitted to load. It is one of the most powerful XSS mitigations available — and one of the most commonly misconfigured, rendering it useless in production. Getting CSP right in 2026 means understanding nonces, strict-dynamic, and why domain allowlists are almost always wrong.
What changed in 2026
strict-dynamic is now the recommended approach. Instead of maintaining an ever-growing domain allowlist, you issue a per-request nonce and trust only scripts that carry it. strict-dynamic then propagates trust to dynamically loaded scripts.
- Browser support for CSP Level 3 is universal. All modern browsers support nonces,
strict-dynamic, and report-to. IE is gone; no compatibility excuses remain.
- Trusted Types graduated to a stable standard. Trusted Types enforce that dangerous DOM APIs (like
innerHTML) only accept pre-sanitized values, complementing CSP.
- Reporting APIs matured. The
Reporting-Endpoints header and report-to directive replaced the older report-uri, giving richer violation reports with batching.
CSP directives that matter most
| Directive |
Controls |
Recommended value |
default-src |
Fallback for unlisted directives |
'self' |
script-src |
JavaScript sources |
'nonce-{n}' 'strict-dynamic' |
style-src |
CSS sources |
'self' 'nonce-{n}' |
img-src |
Image sources |
'self' data: https: |
connect-src |
XHR/fetch/WebSocket |
'self' https://api.myapp.com |
frame-ancestors |
Who can iframe your page |
'none' (prevent clickjacking) |
base-uri |
Restricts <base> element |
'self' |
object-src |
Plugins (<object>, <embed>) |
'none' |
upgrade-insecure-requests |
Force HTTPS for subresources |
Include always |
A nonce-based strict policy
# Flask example — generate a fresh nonce per request
import secrets
from flask import Flask, render_template_string, g
app = Flask(__name__)
@app.before_request
def set_nonce():
g.csp_nonce = secrets.token_urlsafe(16)
@app.after_request
def add_csp_header(response):
nonce = g.csp_nonce
csp = (
f"default-src 'self'; "
f"script-src 'nonce-{nonce}' 'strict-dynamic' https:; "
f"style-src 'self' 'nonce-{nonce}'; "
f"img-src 'self' data: https:; "
f"connect-src 'self' https://api.myapp.com; "
f"object-src 'none'; "
f"base-uri 'self'; "
f"frame-ancestors 'none'; "
f"upgrade-insecure-requests; "
f"report-to csp-endpoint"
)
response.headers['Content-Security-Policy'] = csp
response.headers['Reporting-Endpoints'] = (
'csp-endpoint="https://myapp.com/csp-report"'
)
return response
<!-- In your template, every inline script gets the nonce -->
<script nonce="{{ g.csp_nonce }}">
// This script is trusted
initApp();
</script>
Every request gets a new random nonce. An attacker who injects <script> tags without the nonce gets blocked by the browser.
How to start
- Deploy in
Content-Security-Policy-Report-Only mode first. Add the header with your target policy and a report endpoint. Collect violations for at least two weeks before enforcing.
- Eliminate
unsafe-inline from scripts. Replace inline <script> tags and onclick attributes with external scripts or event listeners. Add nonces to any remaining inline scripts.
- Replace
unsafe-eval. Audit your code for eval(), new Function(), and libraries that use them (some template engines, older jQuery plugins). Replace or remove them.
- Switch domain allowlists to nonces. A policy with
script-src https://cdn.example.com trusts everything on that CDN — including any script an attacker could inject there. Nonces are per-request and unforgeable.
- Enable enforcement and monitor reports. Violations after enforcement indicate something was missed — fix the source or adjust the policy.
Common mistakes
Using 'unsafe-inline'. This permits all inline scripts, which is precisely what XSS attacks inject. A policy with unsafe-inline provides essentially no XSS protection.
Using 'unsafe-eval'. Permits eval(), which allows turning arbitrary strings into executable code. Many classic XSS payloads rely on eval. Eliminate eval from your codebase.
Domain allowlists instead of nonces. script-src https://cdnjs.cloudflare.com trusts every file on that CDN — an enormous surface. If one of those files has a vulnerable version or an attacker can host their payload there, your CSP is bypassed.
Forgetting object-src 'none'. Flash and plugin content (even mostly dead) can bypass script restrictions. Always explicitly deny it.
Not testing in report-only mode. Enforcing a first-draft CSP on a complex app will break your analytics, chat widgets, and payment forms. Report-only mode lets you find those before users do.
What to skip
- CSP alone as your XSS defense. Input sanitization and output encoding are the primary defense; CSP limits the blast radius when they fail. Never skip sanitization because "we have CSP."
- Hand-writing CSP for complex SPAs with server-side rendering and dozens of third-party scripts without tooling. Use a CSP generator or a middleware library that handles nonce injection for your framework.
- Ignoring the
frame-ancestors directive. Clickjacking is separate from XSS; frame-ancestors 'none' prevents your app from being embedded as an iframe and used in a click-jacking attack.
FAQ
Will CSP break Google Analytics or other third-party scripts?
Yes, if configured without those sources. Add third-party scripts via nonce (if you control the HTML injection) or via a hash. Avoid adding entire domains to script-src if possible — prefer nonces.
What is Trusted Types and do I need it?
Trusted Types is a browser API that forces dangerous DOM sinks (like innerHTML) to accept only sanitized TrustedHTML objects rather than raw strings. It pairs well with CSP; a strict CSP + Trusted Types together make DOM-based XSS extremely difficult. It's enabled via the require-trusted-types-for 'script' directive.
How do I handle inline styles for CSS-in-JS?
CSS-in-JS libraries (styled-components, Emotion) can inject inline <style> blocks. Options: generate a hash for each injected style block (some libraries support this), use style nonces, or configure the library to extract static styles at build time.
What CSP grade should I aim for?
The Google CSP Evaluator (still the best free tool in 2026) grades policies. Aim for no "high severity" findings. A nonce-based policy with strict-dynamic typically scores well; domain allowlist policies do not.
Where to go next
JWT best practices in 2026, OAuth explained in 2026, and Feature flags guide in 2026.