Content Security Policy

Content Security Policy (CSP) is a response header that tells the browser which scripts, styles, images, frames and connections a document may use. If an attacker slips <script> or <img onerror=...> into your HTML, a good policy stops it from executing. A policy is a semicolon-separated list of directives, each with a source list. CSP has been Baseline Widely available since 2016; the current specification is CSP Level 3.

The most useful CSP directives
Directive What it controls
default-src Fallback for every fetch directive not listed
script-src, style-src, img-src, font-src Scripts, stylesheets, images, fonts
connect-src fetch(), XHR, WebSocket, EventSource
frame-src, object-src Frames; <object> and <embed>
base-uri, form-action <base href>; where forms may submit
frame-ancestors Which sites may frame this page (clickjacking)
report-to Reporting endpoint for violations
require-trusted-types-for DOM XSS sinks (Sandboxing and Trusted Types)

Sources are keywords in single quotes or bare hosts: 'self' (same origin), 'none', https://cdn.example, *.example.com, https:, 'nonce-…' and 'sha256-…' (nonce), 'unsafe-inline' (any inline code), 'unsafe-eval' (eval() and new Function()) and 'wasm-unsafe-eval' (WebAssembly only). Older references list plugin-types and navigate-to; neither is in Level 3, and report-uri is deprecated in favor of report-to.

Allowlists fail; strict policies work

Early policies listed trusted hosts, such as script-src 'self' 'unsafe-inline' https://apis.google.com 1 .... In 2016 Google engineers analyzed 26,011 distinct policies from 1.68 million hosts and found 94.72% of them bypassable: 'unsafe-inline' lets injected code run, and big allowlisted domains host JSONP endpoints and old library versions that attackers can repurpose. The same paper introduced 'strict-dynamic', the basis of today's recommended strict CSP:

A strict policy rolled out in report-only mode first
Reporting-Endpoints: csp="https://example.com/csp-reports"
Content-Security-Policy-Report-Only:
  script-src 'nonce-{RANDOM}' 'strict-dynamic' https: 'unsafe-inline';
  object-src 'none'; base-uri 'none'; frame-ancestors 'self';
  report-to csp; report-uri https://example.com/csp-reports

The header is sent on one line; it is wrapped here for reading. The nonce approves your scripts, and 'strict-dynamic' extends that trust to scripts they create with document.createElement('script'), so tag managers and lazy-loaded bundles work without a host list. https: and 'unsafe-inline' are fallbacks for old browsers: any browser that understands nonces ignores 'unsafe-inline', and one that understands 'strict-dynamic' ignores https:. object-src 'none' disables plugins, base-uri 'none' stops an injected <base> from redirecting relative script URLs, and frame-ancestors replaces X-Frame-Options.

Roll out in stages: send the -Report-Only header, collect reports, fix your inline handlers, then enforce. Helmet 10,736 (https://github.com/helmetjs/helmet 10,736 ) (MIT, npm 2,036 install helmet) sets CSP and a dozen other security headers in Express 24,430 with app.use(helmet()), and Google's CSP Evaluator 659 (https://csp-evaluator.withgoogle.com/ 659 ) (Apache-2.0) flags bypassable allowlists before you deploy. A frequent interview question asks why 'unsafe-inline' in script-src is pointless against XSS; the answer is that a typical XSS payload is inline script.