Content Security Policy is arguably the most powerful browser security feature available, and also the most likely to break your site the moment you enable it. I have seen production deployments go sideways because someone added a CSP header without understanding the cascading effects.
The concept is simple. The implementation is where it gets interesting.
What CSP does
A CSP header tells the browser which sources of content are allowed on your page. Scripts, styles, images, fonts, frames, connections -- every type of resource can be restricted to specific origins.
Without CSP, a cross-site scripting (XSS) vulnerability lets an attacker inject any script from any source. With a properly configured CSP, even if an attacker finds an injection point, the browser refuses to execute the script because it does not come from an allowed source.
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'
This header says: by default, only load resources from my own origin. Scripts can come from my origin or cdn.example.com. Styles can come from my origin and inline styles are allowed.
The directives you need to know
default-src: The fallback for any directive not explicitly set. Start here.
script-src: Where JavaScript can be loaded from. The most security-critical directive.
style-src: Where CSS can be loaded from. Less critical than script-src but still important.
img-src: Where images can be loaded from. Often needs to allow data: URIs for inline images.
connect-src: Where fetch/XHR/WebSocket connections can go. Critical for APIs.
font-src: Where fonts can be loaded from. Google Fonts requires allowing fonts.gstatic.com.
frame-src: Where iframes can load from. Important if you embed YouTube, maps, or third-party widgets.
media-src: Where audio and video can load from.
object-src: Where plugins (Flash, Java) can load from. Should almost always be 'none'.
The implementation strategy
Do not start with an enforcing CSP. Start with Content-Security-Policy-Report-Only:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-reports
This tells the browser to log violations without blocking anything. Deploy this, let it run for a week, and review the reports. You will discover every third-party resource your site loads, many of which you probably forgot about.
Common discoveries during report-only mode:
- Analytics scripts (Google Analytics, Mixpanel, Segment)
- Font services (Google Fonts, Adobe Fonts)
- Widget embeds (YouTube, Vimeo, Twitter, chat widgets)
- CDN-hosted libraries (cdnjs, unpkg, jsDelivr)
- Payment processors (Stripe, PayPal)
- Inline scripts and styles (often from CMS themes or plugins)
Once you have catalogued every legitimate source, build your enforcing CSP that allows exactly those sources and nothing else.
The inline script problem
The biggest pain point is unsafe-inline for scripts. Many websites, especially those using CMS platforms, inject inline scripts throughout the page. A strict CSP blocks all inline scripts unless you explicitly allow them.
The proper solutions, in order of preference:
Nonces: Generate a random token per request, add it to the CSP header and to each inline script tag.
script-src 'nonce-abc123'allows only scripts with<script nonce="abc123">. The nonce changes every request, so attackers cannot predict it.Hashes: Hash the content of each inline script and add the hash to the CSP.
script-src 'sha256-abc...'allows only scripts whose content matches that exact hash. Works for static inline scripts.'strict-dynamic': Allows scripts loaded by already-trusted scripts. Useful with module loaders and frameworks that dynamically inject scripts.
'unsafe-inline': Allows all inline scripts. This effectively disables XSS protection for scripts. Use only as a last resort.
Building your CSP
The combinatorial complexity of CSP directives across all your third-party integrations makes manual header construction error-prone. I built a CSP header generator that lets you select your services and requirements and generates a properly formatted header with the correct directives.
I'm Michael Lip. I build free developer tools at zovo.one. 500+ tools, all private, all free.
Top comments (0)