HTTP Security Headers

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:

HTTP RESPONSE HEADER
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-endpoint

The 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-ancestors and base-uri close 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' to script-src to 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' and base-uri 'self', which leaves plugin and base-tag injection vectors open even with a tight script-src.
  • Setting default-src alone and assuming it covers everything — several directives (e.g. frame-ancestors, base-uri, form-action) do NOT fall back to default-src.

How WebInspect checks this

  • WebInspect parses the Content-Security-Policy header, 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' in script-src, or a wildcard (*) source on a sensitive directive.
  • It checks for the presence of frame-ancestors, base-uri and object-src and notes when a policy relies on host allow-lists that 'strict-dynamic' would harden.