Content Security Policy: A Practical Implementation Guide
How does CSP prevent cross-site scripting?
Without CSP, a browser executes any script it encounters in the page - including scripts injected by attackers through XSS vulnerabilities. CSP changes this default behaviour.
Consider a page with this CSP header:
Content-Security-Policy: script-src 'self' https://cdn.example.com
This policy tells the browser: only execute scripts loaded from the same origin ('self') or from cdn.example.com. If an attacker injects <script>alert('XSS')</script> into the page through a stored XSS vulnerability, the browser blocks it because the inline script is not from an allowed source.
CSP does not fix the XSS vulnerability - the application code is still flawed. But it prevents the vulnerability from being exploited, buying time for a proper code fix.
How do you deploy CSP step by step?
Phase 1: Audit content sources.
Before writing any policy, catalogue every script, stylesheet, image, font, and iframe source on your site:
- First-party scripts and styles
- CDN-hosted libraries (jQuery, React, Bootstrap)
- Analytics scripts (Google Analytics, PostHog, Segment)
- Advertising and tracking scripts
- Third-party widgets (chat, support, social)
- Embedded content (YouTube, Vimeo, maps)
Phase 2: Deploy in report-only mode.
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; report-uri /csp-report
This reports violations without blocking anything. Collect reports for 1-2 weeks to identify all legitimate sources.
Phase 3: Analyse and adjust.
Review violation reports. Each report identifies a blocked resource and its source URL. Add legitimate sources to the policy. Investigate unexpected sources - they may be injected scripts, tracking pixels, or browser extensions.
Phase 4: Switch to enforcement.
Replace Content-Security-Policy-Report-Only with Content-Security-Policy. The policy now blocks content not in the allowlist. Keep report-uri active to monitor for new violations from site changes or new integrations.
What are the essential CSP directives?
| Directive | Controls | Example |
|---|---|---|
default-src | Fallback for all resource types | 'self' |
script-src | JavaScript execution | 'self' 'nonce-abc123' |
style-src | CSS loading | 'self' 'unsafe-inline' |
img-src | Image loading | 'self' data: https: |
font-src | Font loading | 'self' https://fonts.gstatic.com |
connect-src | XHR, fetch, WebSocket | 'self' https://api.example.com |
frame-src | Iframe sources | https://www.youtube.com |
frame-ancestors | Who can frame this page | 'none' |
base-uri | Allowed base URLs | 'self' |
form-action | Form submission targets | 'self' |
A minimal starting policy:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'
How SurfaceLoop handles this
SurfaceLoop checks CSP headers across all your domains and subdomains. It identifies missing CSP policies, policies that use unsafe-inline for script-src, and overly permissive configurations. Each finding includes the specific policy change needed to fix the issue.
See Security Headers feature →Frequently asked questions
- What is Content-Security-Policy? +
- Content-Security-Policy (CSP) is an HTTP response header that tells browsers which sources of content (scripts, styles, images, fonts, frames) are allowed on a page. It prevents cross-site scripting (XSS) attacks by blocking execution of scripts not in the allowlist, even when an XSS vulnerability exists in the application code.
- How do I start implementing CSP without breaking my site? +
- Start with Content-Security-Policy-Report-Only instead of Content-Security-Policy. This mode reports violations without blocking content, letting you identify all legitimate script and resource sources before switching to enforcement mode. Use a reporting endpoint to collect and analyse violations.
- What is the difference between CSP nonces and hashes? +
- Nonces are random tokens generated per page load and added to both the CSP header and each inline script tag. Hashes are SHA-256 digests of the inline script content added to the CSP header. Nonces are easier to manage for dynamic content; hashes work well for static inline scripts that never change.
- Why should I avoid using unsafe-inline in CSP? +
- The unsafe-inline directive allows all inline scripts to execute, which defeats the primary purpose of CSP. Most XSS payloads are inline scripts - if unsafe-inline is allowed, CSP provides no protection against them. Use nonces or hashes instead to allow specific inline scripts while blocking injected ones.
Get SurfaceLoop security briefings
No spam, just findings that matter. Fortnightly.