◇ Web Security

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-src sets the fallback policy for most resource types.
  • script-src controls where scripts may load from and is the most important directive for stopping XSS.
  • style-src, img-src, font-src, and connect-src govern stylesheets, images, fonts, and network calls such as fetch and WebSocket connections.

Document and Navigation Directives

  • base-uri restricts the <base> element, preventing attackers from changing how relative URLs resolve.
  • frame-ancestors specifies who may embed the page, replacing older framing protections and defending against clickjacking.
  • form-action limits where forms may submit.

Reporting Directives

  • report-to, and the older report-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.

  1. Start in report-only mode. The Content-Security-Policy-Report-Only header enforces nothing but reports what would have been blocked, letting you find legitimate resources.
  2. Analyze reports. Use the violation data to build an accurate policy without disrupting users.
  3. 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.
  4. Tighten and enforce. Switch to the enforcing header, keep object-src 'none' and base-uri 'none', and remove any remaining 'unsafe-inline'.
  5. 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-src directive 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.
cspweb-securitysecurity-headersxssbrowser-security

Frequently asked questions

What is a Content Security Policy (CSP)?

A Content Security Policy is a browser security feature, delivered through an HTTP response header, that tells the browser which sources of scripts, styles, images, and other content are allowed to load and run on a page. By restricting where executable content can come from, it acts as a safety net that can stop injected scripts from running even if a cross-site scripting flaw exists. It is configured by the site and enforced by the browser.

How does a Content Security Policy work?

A CSP works through directives that define allowed sources for each type of resource, such as script-src for JavaScript and style-src for CSS. The browser blocks any resource that does not match the policy and can report violations to a monitoring endpoint. Because the browser enforces the rules, an attacker who injects a script still fails if that script's source or inline execution is not permitted by the policy.

How do you prevent XSS with a Content Security Policy?

To mitigate XSS, use a CSP that avoids unsafe-inline and unsafe-eval and instead allows inline scripts only through per-request nonces or content hashes, so injected inline code will not execute. A strict, nonce-based policy combined with a restrictive default source limits script execution to vetted code. CSP is a strong secondary defense that reduces impact, but it complements rather than replaces proper output encoding.

What is the difference between CSP and CORS?

A Content Security Policy restricts what content a page is allowed to load and execute, protecting users from injected or unauthorized resources. CORS, or Cross-Origin Resource Sharing, controls whether a page from one origin may read responses from another origin's resources. CSP is about limiting a page's own content sources, while CORS is about relaxing the same-origin policy for cross-origin data access, so they solve different problems.

What is a CSP nonce?

A CSP nonce is a random, unpredictable value the server generates for each response and places both in the Content Security Policy header and on the specific inline scripts it trusts. The browser runs only inline scripts carrying the matching nonce, so attacker-injected scripts without it are blocked. Because a fresh nonce is issued per request, attackers cannot reuse or guess it, making nonces a robust way to allow trusted inline code.

Why should a Content Security Policy avoid unsafe-inline?

Allowing unsafe-inline lets any inline script or style on the page execute, which defeats much of a CSP's value because injected inline payloads would run freely. Removing unsafe-inline and using nonces or hashes ensures only explicitly trusted inline code executes. This is why strict policies treat unsafe-inline and unsafe-eval as things to eliminate wherever possible.

Try it hands-on

K0G is an open toolkit of browser-based security utilities — hashing, encoding, JWT, certificates, crypto and more, all running locally in your browser.

Explore the tools →