Content Security Policy (CSP) is a browser security mechanism that controls which resources a web page is allowed to load. By declaring a policy via HTTP header, you tell the browser exactly which scripts, styles, images, fonts, and connections are permitted. Anything not explicitly allowed is blocked.

CSP is one of the most effective defenses against Cross-Site Scripting (XSS) and data injection attacks.

How CSP Works

CSP is delivered as an HTTP response header:

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com

When the browser receives this header, it enforces the policy for the entire page. If a script tries to load from an unauthorized origin, the browser blocks it and logs a violation.

Key Directives

DirectiveControls
default-srcFallback for all resource types
script-srcJavaScript sources
style-srcCSS sources
img-srcImage sources
connect-srcXHR, fetch, WebSocket destinations
font-srcFont file sources
frame-srcIframe sources
media-srcAudio and video sources
object-srcPlugin sources (Flash, Java)
base-uriAllowed <base> URLs
form-actionForm submission targets
frame-ancestorsWho can embed this page (replaces X-Frame-Options)

Example Policies

Strict policy — only allow resources from the same origin:

Content-Security-Policy: default-src 'self'

Allow a CDN for scripts and styles:

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' https://cdn.example.com

Allow inline styles but not inline scripts:

Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'

Nonces vs Hashes

Instead of allowing 'unsafe-inline' (which defeats much of CSP’s purpose), use nonces or hashes to authorize specific inline scripts:

Nonce approach — generate a random value per request:

Content-Security-Policy: script-src 'nonce-abc123'
<script nonce="abc123">
  // This script is allowed because the nonce matches
</script>

Hash approach — allow a specific script by its SHA-256 hash:

Content-Security-Policy: script-src 'sha256-RFWPLDbv2BY+rCkDzsE+0fr8ylGr2R2faWMhq4lfEQc='

Nonces are generally preferred because they don’t require recomputing hashes when script content changes.

Report-Only Mode

Test a policy without enforcing it:

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

The browser logs violations but doesn’t block anything. This lets you identify what would break before deploying the policy.

Common Mistakes

  • Using 'unsafe-inline' for scripts — this allows any inline script, including injected XSS payloads. Use nonces or hashes instead.
  • Using 'unsafe-eval' — allows eval(), Function(), and other dynamic code execution. Avoid unless absolutely necessary.
  • Overly broad wildcardsscript-src * or script-src *.googleapis.com may include domains that host user-controlled content, creating bypass vectors.
  • Forgetting default-src — if you don’t set a fallback, undeclared resource types have no restrictions.
  • Not using object-src 'none' — Flash and other plugins are legacy attack vectors. Block them explicitly.

A Starter Policy

A reasonable starting point for most sites:

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'

Then tighten it by removing 'unsafe-inline' for styles and adding nonces for any inline scripts.

For deeper analysis of CSP headers — parsing, finding bypasses, and scoring policies at scale — see my csp-toolkit post and the csp-toolkit library.


See also: