SSL, TLS, security headers
Deploy a Content-Security-Policy to mitigate XSS attacks
No CSP header means any injected script runs with full page privileges. Add a strict policy with nonce-based script-src, frame-ancestors, and upgrade-insecure-requests.
What's happening
Content-Security-Policy (CSP) is an HTTP response header that constrains which sources the browser will load resources from and which inline content it will execute. Without CSP, an attacker who manages to inject a tag — via stored XSS, reflected XSS, or DOM-based XSS — can execute arbitrary JavaScript with the same privileges as the page itself.
A correctly configured CSP turns most XSS bugs into defense-in-depth opportunities rather than full account takeover. Strict CSP using nonces or hashes for inline scripts, plus 'strict-dynamic' to allow third-party scripts loaded by trusted bootstrap code, is the modern recommendation from Google's CSP team.
The fix is to deploy CSP in Report-Only mode first, fix the violations it surfaces, then promote to enforce mode. Skipping the Report-Only stage almost always breaks production analytics, payment widgets, or admin dashboards.
Why it matters
Without CSP, every XSS bug — even one in a low-traffic admin page — can be escalated to full session takeover, credential theft, or supply-chain compromise. CSP turns a high-severity bug into a medium-severity one.
OWASP ASVS Level 2 requires CSP on all production applications. PCI-DSS 4.0 lists CSP under script integrity requirements for payment pages. Bug-bounty programs accept missing-CSP findings as a separate issue from the underlying XSS.
Mozilla Observatory, SecurityHeaders.com, and most internal security scorecards penalize a missing CSP heavily — typically a full letter grade.
Common causes
- The application has never deployed CSP and the team has not allocated time to identify all script sources.
- An old CSP was removed because it broke production and was never re-introduced safely.
- Inline scripts and styles are everywhere, making strict CSP non-trivial.
- Third-party widgets (analytics, ads, chat, A/B testing) load arbitrary scripts and the team has not enumerated them.
- Single-page application uses eval() or new Function() somewhere in the bundler output.
Detect this on your site
Run a quick scan with the Full Site Audit. The tool surfaces this exact issue with the records and context needed to apply the fix below.
Open Full Site AuditHow to fix it
- 1
Inventory script sources
Open the page in Chrome DevTools, switch to the Network tab, filter by JS, and list every distinct host. Add CSS, font, image, and connect (XHR/fetch) origins. This gives you the source allowlist for the directives.
- 2
Deploy a Report-Only header first
Set
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'nonce-RANDOM'; report-uri /csp-reportand roll it to production. Collect violations for at least one full week to catch all user paths. Use a service like report-uri.com or roll your own collector. - 3
Tighten with nonces, not allowlists
Modern strict CSP is
script-src 'nonce-{random}' 'strict-dynamic';. Generate a fresh nonce per request and set it on every legitimate inline script via the nonce attribute. 'strict-dynamic' allows scripts loaded by trusted scripts, eliminating the need for long allowlists. - 4
Add structural directives
Set
frame-ancestors 'none'(or 'self') to replace X-Frame-Options. Setbase-uri 'self'to prevent base-tag injection. Setform-action 'self'to prevent form-action hijacking. Setupgrade-insecure-requeststo clean up residual mixed content. - 5
Promote from Report-Only to enforce
After the violation rate has dropped to zero or near-zero, change the header from Content-Security-Policy-Report-Only to Content-Security-Policy. Keep the report-uri so you catch regressions.
- 6
Set the header in your web server
In nginx:
add_header Content-Security-Policy "..." always;. In Apache:Header always set Content-Security-Policy "...". In Next.js, set it in middleware or next.config.js headers(). Generate the nonce per request in the rendering layer. - 7
Re-audit and lock in
Re-run the Full Site Audit at /check and verify the CSP is parsed and enforced. Mozilla Observatory should grade A+. Add a CI test that ensures the header exists on every response.
Example
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-r4nd0mB4se64=' 'strict-dynamic'; style-src 'self' 'nonce-r4nd0mB4se64='; img-src 'self' data: https:; connect-src 'self' https://api.example.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; upgrade-insecure-requests; report-uri https://example.com/csp-report
Strict nonce-based CSP suitable for production
Frequently asked
Yes — every modern browser since 2016 enforces nonce-based CSP. The nonce attribute on the script tag must match the nonce in the header character for character. The nonce should be at least 128 bits of entropy and rotated per request.
You can but it neutralizes most of the XSS protection. Modern frameworks (Next.js Remix Rails 7) support nonce injection in templates and CSPs. Migrate the remaining inline content over a few sprints rather than living with 'unsafe-inline' permanently.
Related fixes
SSL, TLS, security headers
Remove unsafe-inline from CSP and adopt nonce-based scripts
SSL, TLS, security headers
Block clickjacking with X-Frame-Options or CSP frame-ancestors
SSL, TLS, security headers
Fix mixed content warnings on HTTPS pages
SSL, TLS, security headers
Set Referrer-Policy to control outbound Referer leakage