Content-Security-Policy (CSP)
The single most effective defense against cross-site scripting: a policy that tells the browser exactly which sources of scripts, styles, images and other resources are allowed to load.
What it is
Content-Security-Policy (CSP) is a response header that lets a site declare an allow-list of trusted content sources. The browser enforces the policy: any script, stylesheet, image, font, frame or connection that violates it is blocked, and — if report-uri/report-to is set — a violation report is sent. CSP is defined by the W3C CSP Level 3 specification and is the primary browser-side mitigation for cross-site scripting (XSS).
A policy is a semicolon-separated list of directives, each naming a resource type followed by its allowed sources.
Syntax & key directives
- default-src
- Fallback allow-list for any fetch directive not explicitly set.
- script-src
- Valid sources for JavaScript. The most security-critical directive.
- style-src
- Valid sources for stylesheets and inline styles.
- img-src
- Valid sources for images.
- connect-src
- Restricts fetch/XHR/WebSocket/EventSource targets.
- frame-ancestors
- Which origins may embed this page in a frame — a modern replacement for X-Frame-Options.
- base-uri
- Restricts the URLs usable in a document's <base> element (prevents base-tag injection).
- object-src
- Sources for <object>/<embed>. Set to 'none' unless you serve plugins.
- 'nonce-...'
- A per-response random token that whitelists a specific inline <script>/<style>.
- 'strict-dynamic'
- Trusts scripts loaded by an already-trusted script, so you can drop host allow-lists.
Avoid 'unsafe-inline' and 'unsafe-eval'
'unsafe-inline' in script-src re-allows inline scripts and event handlers — the exact vector CSP is meant to block. Prefer nonces or hashes. 'unsafe-eval' re-enables eval() and should almost never be present.
Example
A strong, nonce-based policy for a modern app:
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-r4nd0m' 'strict-dynamic'; style-src 'self'; img-src 'self' data: https:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; report-to csp-endpointThe matching inline script must carry the same nonce: <script nonce="r4nd0m">. Roll a fresh nonce on every response — a static nonce is worthless.
Deploying safely with report-only
Ship a new policy as Content-Security-Policy-Report-Only first. It reports violations without blocking anything, so you can watch real traffic and tighten the policy before enforcing it. Both headers can be sent at once — enforce a known-good baseline while report-only tests a stricter candidate.
Why it matters
- A correct CSP neutralizes most reflected and stored XSS: even if an attacker injects a
<script>tag, the browser refuses to run it because it lacks a valid nonce or an allow-listed source. frame-ancestorsandbase-uriclose clickjacking and base-tag hijacking that a naive header set would miss.- Violation reports turn the policy into a live intrusion-detection signal — you learn about injection attempts and broken third-party integrations from real users.
Common mistakes
- Adding
'unsafe-inline'toscript-srcto make a legacy page work — this defeats the entire purpose of CSP. - Using a static or reused nonce. A nonce only protects a response if it is unguessable and unique to that response.
- Forgetting
object-src 'none'andbase-uri 'self', which leaves plugin and base-tag injection vectors open even with a tightscript-src. - Setting
default-srcalone and assuming it covers everything — several directives (e.g.frame-ancestors,base-uri,form-action) do NOT fall back todefault-src.
How WebInspect checks this
- WebInspect parses the
Content-Security-Policyheader, flags a missing policy as a high-severity gap and lists every directive it finds. - It downgrades the grade when it detects
'unsafe-inline'or'unsafe-eval'inscript-src, or a wildcard (*) source on a sensitive directive. - It checks for the presence of
frame-ancestors,base-uriandobject-srcand notes when a policy relies on host allow-lists that'strict-dynamic'would harden.