Content Security Policy (CSP): A Practical Guide
Content Security Policy, or CSP, is a browser security mechanism that lets a site declare which sources of content are allowed to load and execute. Delivered as an HTTP response header, a well-crafted CSP dramatically reduces the impact of cross-site scripting and related injection attacks by preventing the browser from running unauthorized scripts, even if an attacker manages to inject them. It acts as a defense-in-depth layer that catches failures in output encoding and sanitization.
How Content Security Policy Works
When a browser receives a Content-Security-Policy header, it enforces the listed rules for that page. The policy is a series of directives, each naming a resource type and the origins permitted for it. If content violates the policy, the browser refuses to load or execute it and can report the violation.
A basic but meaningful policy looks like this:
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-r4nd0m'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'
This example allows resources only from the site's own origin by default, permits scripts from the origin or those carrying the matching nonce, forbids plugins, prevents <base> tag hijacking, and blocks the page from being framed.
Key Directives
Fetch Directives
default-srcsets the fallback policy for most resource types.script-srccontrols where scripts may load from and is the most important directive for stopping XSS.style-src,img-src,font-src, andconnect-srcgovern stylesheets, images, fonts, and network calls such as fetch and WebSocket connections.
Document and Navigation Directives
base-urirestricts the<base>element, preventing attackers from changing how relative URLs resolve.frame-ancestorsspecifies who may embed the page, replacing older framing protections and defending against clickjacking.form-actionlimits where forms may submit.
Reporting Directives
report-to, and the olderreport-uri, tells the browser where to send violation reports, which is invaluable for tuning a policy and detecting attacks.
Additional Hardening Directives
Several directives harden the page further. upgrade-insecure-requests asks the browser to fetch resources over HTTPS, frame-src limits what the page may embed, and object-src 'none' disables legacy plugin content that has served as a bypass for script restrictions. Locking down object-src and base-uri closes common gaps that attackers use to sidestep an otherwise strict script-src, so include them even in a minimal policy.
Writing a Strong Policy
Avoid unsafe-inline and unsafe-eval
The keyword 'unsafe-inline' permits inline scripts and event handlers, which is exactly what most XSS payloads use. A policy that allows it provides little protection. Similarly, 'unsafe-eval' permits dynamic code evaluation. A strong policy avoids both.
Use Nonces or Hashes
To allow specific inline scripts without opening the door to all of them, use a nonce or a hash. A nonce is a random value generated per response and placed on both the header and the legitimate <script> tag; the browser runs only scripts bearing the matching nonce. A hash pins the exact contents of an inline script. Both let you keep necessary inline code while blocking injected scripts.
Adopt strict-dynamic
The 'strict-dynamic' keyword trusts scripts loaded by an already-trusted script, which simplifies policies for applications that load code dynamically. Combined with nonces, it enables a policy that does not depend on maintaining long allowlists of host names, which are easy to get wrong.
Content-Security-Policy: script-src 'nonce-r4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'none'
Deploying CSP Safely
Rolling out a strict policy on an existing site can break functionality, so a staged approach works best.
- Start in report-only mode. The
Content-Security-Policy-Report-Onlyheader enforces nothing but reports what would have been blocked, letting you find legitimate resources. - Analyze reports. Use the violation data to build an accurate policy without disrupting users.
- Refactor inline code. Move inline scripts and handlers into separate files or attach nonces, since a nonce-based policy is far stronger than a host allowlist.
- Tighten and enforce. Switch to the enforcing header, keep
object-src 'none'andbase-uri 'none', and remove any remaining'unsafe-inline'. - Monitor continuously. Keep collecting reports to catch regressions and possible attacks.
Limitations to Understand
CSP is a powerful mitigation but not a complete fix. It reduces the impact of XSS rather than eliminating the underlying bug, so output encoding and sanitization remain essential. Policies must be maintained as the application changes, and an overly permissive policy offers a false sense of security. CSP also cannot protect against vulnerabilities that do not involve loading or executing content, so treat it as one layer within a broader strategy explored across K0G.
Key Takeaways
- Content Security Policy is an HTTP header that tells the browser which content sources are allowed, limiting XSS impact.
- The
script-srcdirective is central; avoid'unsafe-inline'and'unsafe-eval', which undermine protection. - Nonces, hashes, and
'strict-dynamic'allow necessary scripts while blocking injected ones without fragile host lists. - Deploy using report-only mode first, then tighten to an enforcing policy while monitoring reports.
- CSP is defense in depth that reduces impact, not a replacement for fixing the underlying vulnerability.