Content Security Policy (CSP) Without Breaking Your Site
You've heard that Content Security Policy (CSP) is essential for protecting your site from cross-site scripting (XSS) and data injection attacks. But when you try to add it, your site breaks: images disappear, scripts stop running, styles vanish. It feels like a trade-off between security and functionality. It doesn't have to be.
In this guide, you'll learn a practical, step-by-step approach to deploy CSP without breaking your site. We'll cover the core directives, how to use nonces and hashes, and how to test safely. By the end, you'll have a working CSP that enhances security without sacrificing user experience.
What is CSP and Why Does It Break Sites?
Content Security Policy is a browser security standard that lets you restrict which resources (scripts, styles, images, fonts, etc.) can be loaded on your page. It's delivered via an HTTP header like Content-Security-Policy: default-src 'self'.
CSP breaks sites because it blocks any resource that doesn't match your policy. If you have inline scripts, external scripts from CDNs, or inline styles, they'll be blocked unless you explicitly allow them. The default behavior is to block everything not allowed, which is why a strict policy can quickly break a site.
The key is to start with a permissive policy and gradually tighten it while monitoring for violations.
Core CSP Directives You Need to Know
CSP uses directives to control different resource types. Here are the most common ones:
- default-src: Fallback for other directives. Start here.
- script-src: Controls JavaScript sources.
- style-src: Controls CSS sources.
- img-src: Controls image sources.
- connect-src: Controls AJAX, WebSocket, and fetch targets.
- font-src: Controls web font sources.
- frame-src: Controls iframe sources.
- report-uri / report-to: Where to send violation reports.
You can set multiple directives in one header, separated by semicolons. For example:
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://images.example.com
Each directive accepts a space-separated list of sources. Sources can be keywords like 'self', 'unsafe-inline', 'unsafe-eval', or URLs, or nonces/hashes.
Step-by-Step: Deploy CSP Without Breaking Your Site
Follow these steps to roll out CSP safely.
- Start with a report-only policy. Use
Content-Security-Policy-Report-Onlyheader instead of the enforcing one. This lets you see what would be blocked without actually blocking it. - Set a permissive policy. Begin with something like
default-src 'self' 'unsafe-inline' 'unsafe-eval' https:to allow most things. This minimizes breakage. - Collect violation reports. Configure
report-urito an endpoint that logs violations. Review these reports to identify blocked resources. - Fix violations. Update your code to avoid inline scripts/styles, or add nonces/hashes. Move external resources to allowed domains.
- Tighten the policy gradually. Remove
'unsafe-inline'and'unsafe-eval'once you've refactored. Narrow allowed domains. - Switch to enforcing mode. Once reports show no unexpected blocks, change the header to
Content-Security-Policy(without-Report-Only). - Monitor continuously. Keep the report endpoint active to catch new issues.
This incremental approach ensures you don't break your site while improving security.
Using Nonces and Hashes for Inline Scripts
Inline scripts are a common cause of CSP breakage. Instead of allowing 'unsafe-inline', use a nonce (number used once) or hash.
Nonce approach: Generate a random nonce per request, add it to your CSP header, and include it in your script tags.
Content-Security-Policy: script-src 'nonce-abc123'
<script nonce="abc123">...</script>
Hash approach: Compute the SHA hash of your inline script and add it to the policy.
Content-Security-Policy: script-src 'sha256-xyz...'
Hashes are best for static inline scripts that don't change often. Nonces are better for dynamic content.
For styles, you can also use nonces or hashes, but note that style-src with nonces doesn't cover inline style attributes (e.g., style="..."). For those, you need 'unsafe-inline' or to refactor to classes.
Common CSP Directives and Their Impact
| Directive | What It Controls | Common Sources |
|---|---|---|
| default-src | Fallback for all resource types | 'self', https: |
| script-src | JavaScript sources | 'self', 'nonce-...', 'sha256-...', https://cdn.com |
| style-src | CSS sources | 'self', 'unsafe-inline', 'nonce-...' |
| img-src | Image sources | 'self', data:, https://images.com |
| connect-src | AJAX, WebSocket, fetch | 'self', https://api.com |
| font-src | Web fonts | 'self', https://fonts.gstatic.com |
| frame-src | Iframes | 'self', https://youtube.com |
Use this table as a quick reference when building your policy.
Testing and Monitoring Your CSP
Before enforcing, test thoroughly. Use browser developer tools: the Console tab shows CSP violations as errors. The Network tab shows the CSP header.
For automated testing, consider tools like Google's CSP Evaluator (online) or the csp_evaluator npm package. These help identify weak policies.
Set up a report endpoint to collect violations in production. You can use a service like Report URI or build your own endpoint that logs to a file or database. Analyze reports regularly to catch new issues.
Remember: CSP is not a silver bullet. It's one layer of defense. Combine it with input validation, output encoding, and other security best practices.
FAQ
What is the difference between Content-Security-Policy and Content-Security-Policy-Report-Only?
The enforcing header (Content-Security-Policy) blocks violations. The report-only header (Content-Security-Policy-Report-Only) only reports violations without blocking, allowing you to test a policy safely.
Can I use CSP with inline event handlers like onclick?
No, inline event handlers are blocked by CSP unless you use 'unsafe-inline' (which is discouraged) or refactor to use addEventListener. For better security, avoid inline event handlers.
How do I allow Google Analytics with CSP?
Add the Google Analytics domains to your script-src and connect-src directives. For example: script-src 'self' https://www.googletagmanager.com; connect-src 'self' https://www.google-analytics.com. Check Google's documentation for the latest requirements.
Deploying CSP doesn't have to be a painful process. With a gradual approach, you can protect your users from XSS and other attacks without disrupting your site. Start with report-only mode, fix violations, and tighten your policy over time.
If you need to quickly format or validate JSON configuration files for your CSP reports, try our JSON Formatter to beautify and debug your JSON data.