CSP

On this page 8

CSP, short for Content Security Policy, is a browser security mechanism in which a web application sends a declarative allowlist of permitted content sources in an HTTP response header, and the browser refuses to load or execute anything outside that list. In application security it is the main defense-in-depth layer against cross-site scripting (XSS) and related injection attacks.

CSP does not fix vulnerable code. It limits what an attacker can do after an injection succeeds, so a stray script tag inserted into a page is blocked instead of running with the user’s session. It is defined by the W3C Content Security Policy specification, currently at Level 3, and is supported by all major browsers.

How CSP enforcement works in the browser

The server delivers the policy in the Content-Security-Policy response header, or less flexibly in a meta element. The policy is a list of directives, each naming a resource type and the sources allowed for it, for example script-src 'self'. Before the browser fetches a script, image, frame or connection, it checks the request against the matching directive.

When a resource is not allowed, the browser blocks it and records a violation. By default CSP also disables inline scripts and the eval function, because these are exactly what most XSS payloads rely on. A policy can send violation reports to a collection endpoint, which lets teams see what would break before they enforce anything.

A second header, Content-Security-Policy-Report-Only, applies the same rules without blocking. Running a policy in report-only mode for days or weeks is the standard way to discover legitimate resources that a new policy would otherwise break.

Key CSP directives and source expressions

A CSP policy is built from fetch directives, document directives and navigation directives. The table lists those that matter most in practice and what each one controls.

DirectiveWhat it controlsTypical safe value
default-srcFallback for fetch directives not set explicitly‘self’ or ‘none’
script-srcJavaScript sources and inline script execution‘nonce-…’ ‘strict-dynamic’
style-srcStylesheets and inline styles‘self’ with nonces where possible
object-srcPlugins such as embed and object elements‘none’
base-uriAllowed values of the base element‘none’ or ‘self’
frame-ancestorsWhich sites may embed the page in a frame‘none’ or ‘self’
connect-srcFetch, XHR and WebSocket destinations‘self’ plus required APIs
form-actionWhere forms may submit data‘self’
Frequently used CSP directives

Source expressions inside directives include host names, schemes such as https:, and keywords. The keywords ‘unsafe-inline’ and ‘unsafe-eval’ re-enable the dangerous behaviors CSP was meant to stop, so their presence in script-src usually cancels most of the protection.

Allowlist policies versus strict policies

There are two broad styles of CSP. An allowlist policy names trusted domains, while a strict policy trusts individual scripts through nonces or hashes. Research on deployed policies showed that host allowlists are frequently bypassable, because a trusted domain often hosts JSONP endpoints or old library versions an attacker can abuse.

Policy styleHow scripts are trustedStrengthMain drawback
Host allowlistBy domain nameEasy to readOften bypassable through trusted hosts
Nonce-based strict CSPRandom per-response value on each script tagStrong against injected markupRequires server-side templating changes
Hash-based strict CSPSHA-256 to SHA-512 digest of script contentWorks for static pagesEvery script change needs a new hash
strict-dynamicTrust propagates to scripts loaded by trusted scriptsCompatible with loadersNeeds nonces or hashes as an anchor
Comparison of CSP policy styles

A nonce must be unpredictable and regenerated for every response, with at least 128 bits of randomness. A static or cached nonce makes the policy decorative, since an attacker can simply copy it into injected markup.

CSP within the wider application security picture

CSP addresses a specific slice of the risks described in the OWASP Top 10. It is strongest against XSS, helps with clickjacking and data exfiltration, and does almost nothing for server-side flaws. The comparison shows where it fits next to related controls.

ThreatEffect of CSPPrimary control
XSSStrong mitigation when policy is strictOutput encoding and safe templating
ClickjackingPrevented by frame-ancestorsCSP or X-Frame-Options
CSRFNo meaningful effectAnti-CSRF tokens and SameSite cookies
SQL injectionNo effectParameterized queries
Mixed contentFixed by upgrade-insecure-requestsHTTPS everywhere
Data exfiltrationPartial limits via connect-src and img-srcInput validation and monitoring
What CSP does and does not mitigate

The key lesson is that CSP is a second line of defense. Teams that treat it as a substitute for correct output encoding usually end up with permissive policies and real XSS bugs.

Deploying a CSP without breaking the site

Successful CSP rollouts follow an iterative, report-driven process rather than a single switch. The ordered steps below reflect common practice on large web applications.

  1. Inventory scripts, styles, frames and third-party services the application really uses.
  2. Refactor inline event handlers and javascript: URLs into external or nonced scripts.
  3. Deploy the policy in report-only mode and collect violation reports.
  4. Filter noise from browser extensions and fix legitimate violations in the code.
  5. Switch to enforcement, keep reporting enabled and review new violations after each release.

Third-party tags from analytics and advertising are the usual obstacle, since they inject further scripts dynamically. strict-dynamic helps here, because a trusted loader can add its own dependencies without listing every domain.

Common CSP mistakes

Many deployed policies give a false sense of security. The following errors appear repeatedly in security assessments.

  • ‘unsafe-inline’ in script-src, which allows exactly the inline payloads XSS uses.
  • Wildcards such as https: or *.cdn.example, which trust far more code than intended.
  • Missing object-src and base-uri, which leave plugin and base tag injection open.
  • Reused or predictable nonces, often caused by full-page caching.
  • Report-only left in place permanently, so nothing is ever blocked.
  • Conflicting meta and header policies, which combine and break pages unexpectedly.

In everyday language

Think of a web page as a party and CSP as the guest list handed to the doorman, which here is the browser. The site tells the browser in advance which sources of code are invited. If someone sneaks a malicious script into the page, it is not on the list, and the browser simply refuses to let it run.

The guest list does not stop people from trying to get in, and a careless list with too many names helps nobody. Used well, it turns many attacks into harmless blocked attempts.

Seen in the real world

An online bookstore discovers that its product review field once allowed stored XSS. While developers fix the encoding bug, the security team rolls out a nonce-based strict CSP with strict-dynamic, object-src ‘none’ and base-uri ‘none’. It runs in report-only mode for three weeks, during which reports reveal two forgotten inline handlers and an old chat widget.

After those are fixed, the policy is enforced. A month later, a penetration tester finds another injection point in a newsletter form, but the injected script is blocked by the browser and reported, so the finding is rated lower and patched with no customer data exposed.