Blog

CSP Level 3 in Practice: What Changed From CSP2 and What to Check in Your Policy

CSP Level 3 is still a Working Draft, but much of it already ships in browsers. Here is what it changes about reporting, workers, strict-dynamic and hashes, and what to check in your policy.

The W3C has published another Working Draft of Content Security Policy Level 3. Working Drafts rarely make the news. They don't come with a CVE number or a breach headline. But if you run CSP in production, read violation reports, or build tooling around them, CSP3 is worth understanding, because browsers have been implementing its features for years while the document itself has stayed a draft.

CSP3 is not yet a W3C Recommendation, so none of it is a formal requirement. Many of its features already ship in browsers, though, and several affect how you should write your policy and collect reports today. This post covers what CSP3 changes compared with CSP2 and what to check in your own policy.

A spec built on Fetch

CSP3 is written in terms of the Fetch standard and its integration with HTML, rather than a separate vocabulary for requests and resource loading. That makes it easier for browsers to implement consistently, and easier to keep aligned with Service Workers, which fetch and cache resources on a page's behalf. If you've seen a CSP violation that seemed to come from nowhere, such as a prefetch or a service worker request, this alignment is meant to make those cases predictable.

Directive changes

  • frame-src is back. CSP2 deprecated it in favour of child-src. CSP3 undeprecates it as the directive governing nested browsing contexts. If frame-src isn't set, it falls back to child-src, then default-src, so policies that only set child-src keep working.
  • worker-src has its own directive. Workers are no longer covered only by child-src. If worker-src isn't set, workers fall back to child-src, then script-src, then default-src. Browsers already support it.
  • manifest-src controls where a page's web app manifest can be loaded from. It falls back to default-src. Progressive web apps can use it to lock down the manifest file.
  • Scheme upgrades are matched explicitly. A source like http://example.com also matches https://example.com, and port 80 matches port 443 in the upgraded form, which reflects how browsers treat upgrades in practice.

report-uri is deprecated in favour of report-to

CSP3 deprecates the report-uri directive. Its replacement, report-to, names an endpoint group defined through the Reporting API, using either the newer Reporting-Endpoints header or the older Report-To header, rather than putting a bare URL in the CSP header.

In practice:

  • The report format differs. Reporting API reports use a different JSON structure from the classic csp-report object. If you or your vendor parse reports yourselves, support both formats during the transition.
  • Endpoints are declared separately from the policy. One endpoint group can collect reports from several mechanisms, including CSP, COOP, Permissions-Policy and deprecation reports, instead of needing a separate URL for each.
  • Inline violations are identified by the string "inline" as the blocked resource, rather than a URL, which makes them easy to group.
  • Browser support for report-to has been strongest in Chromium-based browsers. Check current support for Firefox and Safari on MDN or caniuse before relying on it alone. Most teams should send both directives for now, with report-uri covering browsers that lack support. report-uri is deprecated but still widely used.

strict-dynamic and hashes

Three CSP3 features matter most for teams running, or moving to, a nonce- or hash-based "strict" policy. This is the approach we generally recommend over long domain allowlists.

'strict-dynamic' propagates trust to scripts that trusted scripts create. A script trusted by a nonce or hash can load further scripts by creating them at runtime, for example with document.createElement and appendChild, and those scripts run without being listed in the policy. Scripts inserted by the HTML parser, such as those written with document.write, do not inherit that trust. When 'strict-dynamic' is present, browsers ignore host allowlists, 'self' and 'unsafe-inline' in script-src. This is why tag managers, analytics loaders and bundler-generated chunks can work under a strict CSP without you listing every domain.

External scripts can match a hash when they carry an integrity attribute. If a <script src="..."> tag has an integrity attribute, a matching hash-source in script-src can allow it, not just inline scripts. Pairing CSP hashes with Subresource Integrity means a third-party script that changes without your knowledge will fail to load. Support for this has varied between browsers, so test it before depending on it.

'unsafe-hashes' allows a hash to match inline event handler attributes (such as onclick="...") and inline style attributes. It lets you permit a small, audited set of inline handlers without falling back to 'unsafe-inline'. Treat it as a migration aid rather than a long-term goal, since moving handlers into external scripts is safer.

What to check now

  1. Send report-to alongside report-uri, not instead of it. Set up a Reporting API endpoint group and add report-to to your policies, keeping report-uri as a fallback.
  2. Make sure your report parsers handle both formats. This applies to your own collector, your SIEM and any monitoring tool, including how inline violations are represented.
  3. Set worker-src and manifest-src explicitly if you run service workers or a PWA manifest, instead of relying on fallbacks. An explicit directive won't change behaviour if you later edit child-src, script-src or default-src.
  4. Pair Subresource Integrity with your script-src hashes on third-party or CDN-hosted scripts where you can pin a version, and test in the browsers you support.
  5. If you're on 'unsafe-inline', plan a move to nonces or hashes with 'strict-dynamic'. Use 'unsafe-hashes' for the few inline handlers you can't remove yet.

None of this needs an emergency change. It fits well into the next sprint where someone is already editing your CSP.

The takeaway

CSP Level 3 has been a Working Draft for years, but browsers have been shipping its features throughout that time. The reporting model, the behaviour of 'strict-dynamic' and hash matching tied to SRI already shape how modern policies are built. Use the draft as a reason to review your own headers. Are you relying on report-uri alone? Are your worker and manifest sources covered? Are your third-party scripts pinned with integrity hashes?

To see where your policy stands today, run your site through our free header scanner, or read the getting started guide to begin collecting CSP reports.