Content Security Policy is the single most effective defence against cross-site scripting. A properly configured CSP makes most XSS payloads structurally impossible — the browser simply refuses to execute scripts that aren't explicitly allowed. Yet CSP adoption remains startlingly low, and the reason is fear. Teams worry that a restrictive policy will break their site, block legitimate scripts, and generate an avalanche of user-facing errors. This guide shows how to deploy CSP incrementally, safely, and without breaking anything.
What CSP Actually Does
CSP is an HTTP response header that tells the browser which sources of content are allowed on your page. When the browser encounters a resource — a script, stylesheet, image, font, or iframe — it checks whether the source is permitted by the policy. If it's not, the resource is blocked and the violation is logged.
The core directives you'll work with:
default-src— The fallback for all resource types not explicitly configured.script-src— Which domains can serve JavaScript. This is the primary XSS defence.style-src— Allowed sources for CSS.img-src— Allowed sources for images.connect-src— Which endpoints the page can make network requests to (fetch, XHR, WebSocket).frame-src— Allowed sources for iframes.font-src— Allowed sources for web fonts.object-src— Controls plugins like Flash (set this to'none'— there's no reason to allow it in 2025).base-uri— Restricts the URLs that can be used in the<base>element, preventing base tag hijacking.form-action— Limits where forms can submit data, blocking form-based data exfiltration.
Step 1: Start with Report-Only Mode
The key to deploying CSP without breaking your site is Content-Security-Policy-Report-Only. This header applies the same policy rules but only reports violations — it doesn't block anything. Your site continues working exactly as before, while you collect data on what a restrictive policy would affect.
Start with a baseline policy:
Content-Security-Policy-Report-Only:
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self';
report-uri /api/csp-reports;
Deploy this and monitor the reports. Every third-party script, external stylesheet, analytics pixel, and CDN-hosted resource will generate a violation report. These reports are your roadmap — they tell you exactly what your policy needs to allow.
Step 2: Build Your Allowlist from Reports
After collecting reports for a few days (enough to capture all page types and user interactions), you'll have a complete picture of your site's external dependencies. Common additions include:
- Analytics providers (Google Analytics, Mixpanel, Segment)
- CDN-hosted libraries (cdnjs, unpkg, jsdelivr)
- Font providers (Google Fonts, Adobe Fonts)
- Payment processors (Stripe.js, PayPal)
- Embedded widgets (YouTube, Vimeo, Twitter, chat widgets)
- Error monitoring services (Sentry, Datadog, LogRocket)
Add each legitimate source to the appropriate directive. Be as specific as possible — allow https://js.stripe.com rather than https://*.stripe.com. Every broad pattern is a wider attack surface.
Step 3: Handle Inline Scripts and Styles
The most disruptive part of CSP is that it blocks inline scripts and styles by default. This is by design — inline scripts are the primary XSS payload delivery mechanism. But many legitimate applications use inline code.
You have three options, ranked from most to least secure:
- Nonces: Generate a unique random nonce for each page load. Add it to the CSP header as
'nonce-abc123'and to each legitimate inline script as<script nonce="abc123">. Only scripts with the matching nonce execute. This is the gold standard. - Hashes: Compute the SHA-256 hash of each inline script's content and add it to the policy as
'sha256-...'. The browser only executes inline scripts whose content matches a whitelisted hash. Works well for static inline scripts that don't change. 'unsafe-inline': Allows all inline scripts. This disables CSP's primary XSS protection and should be avoided. If you must use it temporarily, pair it with'strict-dynamic'to limit the damage.
Step 4: Adopt strict-dynamic for Complex Applications
Modern applications often load scripts dynamically — a main script that loads additional modules at runtime. Maintaining an allowlist for every loaded script is impractical. The 'strict-dynamic' directive solves this: it trusts scripts loaded by already-trusted scripts. Combined with nonces, this means you only need to nonce your entry-point scripts, and everything they load inherits trust.
Content-Security-Policy:
script-src 'nonce-{random}' 'strict-dynamic';
object-src 'none';
base-uri 'self';
With 'strict-dynamic', host-based allowlists in script-src are ignored. Trust flows from nonces and hashes through the script loading chain. This is significantly easier to manage for single-page applications and sites with complex script dependency trees.
Step 5: Enforce and Monitor
Once your report-only policy runs clean for at least a week — zero unexpected violations — switch from Content-Security-Policy-Report-Only to Content-Security-Policy. Keep the report-uri (or the newer report-to) directive active. Policy violations in production are now blocked, but you'll still receive reports to catch regressions.
Common pitfalls after enforcement:
- A new marketing tool is added that loads scripts from an unknown domain — blocked by CSP. Solution: add the source to the policy before deploying the tool.
- A developer adds an inline event handler (
onclick="...") that CSP blocks. Solution: move the handler to an external script file or add a nonce. - A third-party script updates and starts loading resources from a new subdomain. Solution: monitor reports continuously and update the policy as dependencies evolve.
CSP as a Living Policy
CSP isn't a set-and-forget header. It's a living document that reflects your application's real dependency graph. Every new third-party integration, every script change, every CDN migration may require a policy update. The organisations that succeed with CSP treat it as part of the deployment process, not an afterthought.
The payoff is substantial. A well-enforced CSP eliminates entire categories of XSS attacks, limits the damage from compromised third-party scripts, and provides real-time visibility into unexpected code execution on your pages.
ShieldReport monitors your CSP deployment, identifying missing directives, overly permissive sources, and unsafe fallbacks like 'unsafe-inline' — so you can tighten your policy with confidence, knowing exactly what needs to change.