Skip to content

Security · Intermediate · 2 min read

A practical guide to Content-Security-Policy

CSP is the strongest defence against cross-site scripting. Learn what it controls, how to roll it out safely with report-only, and how to avoid unsafe-inline.

A Content-Security-Policy (CSP) tells the browser exactly which sources of script, style, images and other resources are allowed to load. If an attacker injects a <script> tag, a good CSP stops it running, which is why CSP is the single most effective mitigation for cross-site scripting (XSS).

The shape of a policy

A policy is a set of directives, each naming allowed sources. default-src is the fallback; more specific directives override it.

Example
Content-Security-Policy: default-src 'self'; img-src 'self' data:; style-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'

Avoid 'unsafe-inline'

The moment you allow 'unsafe-inline' for scripts, CSP stops protecting you from injected scripts, which is the whole point. Instead, move inline scripts to files served from your own origin, or authorise the specific inline blocks you control with a hash or a per-request nonce.

Example
script-src 'self' 'sha256-BASE64HASH='
# or
script-src 'self' 'nonce-RANDOMPERREQUEST'

Roll it out with report-only

Don't ship a strict policy blind. Deploy it in report-only mode first: the browser reports what would have been blocked without actually blocking anything, so you can find legitimate resources you forgot before enforcing.

Example
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report

Common pitfalls

  • Third-party widgets that inject inline script or load from many hosts: allowlist exactly what they need and nothing broader.
  • connect-src forgetting your analytics or API endpoints, silently breaking requests.
  • Edge providers injecting their own inline scripts that your hash doesn't cover: either disable that feature or account for it.
  • Using a nonce that isn't actually random per response: a fixed nonce is no better than unsafe-inline.

How to fix it

  1. Inventory what your pages load (script, style, images, fonts, connections, frames).
  2. Write a report-only policy and watch the reports for a few days.
  3. Tighten until there are no legitimate violations, then switch the header to enforcing.
  4. Re-scan and keep object-src 'none', base-uri 'self' and frame-ancestors locked down.

Glossary

Directive
One rule inside a CSP, e.g. script-src, naming the allowed sources for a resource type.
Nonce
A random token generated per response and placed on both the header and the trusted inline tag to authorise it.
XSS
Cross-site scripting: running attacker-controlled JavaScript in a victim's browser in the context of your site.

Zenith checks for this automatically.

Scan your site to see whether this applies to you.