← Blog

How CSP script hash reporting works with report-sha256

Add 'report-sha256' to a Content-Security-Policy and Chrome and Edge send a hash of each script they fetch. How to set it up, what the report contains, and where it helps.

Michael Cameron4 min readUpdated

Most Content Security Policy reports tell you what the browser blocked. Script hash reporting tells you what it loaded. Add 'report-sha256' to your policy and Chrome and Edge send a report for each script they fetch, with a SHA-256 hash of the script’s contents.

Your policy becomes a record of the scripts that ran on your pages, the starting point for a script inventory and for the PCI DSS 4.0 script requirements.

What report-sha256 does

'report-sha256' is one of three keywords CSP Level 3 adds for script hash reporting, along with 'report-sha384' and 'report-sha512'. Each one sets the algorithm the browser uses for the hash, and each goes in a directive that controls scripts, such as script-src or script-src-elem.

The keywords don’t allow or block anything. They look like hash sources such as 'sha256-…', which permit a script whose contents match, but a report keyword only asks the browser to tell you what it loaded. The browser computes one hash per report, so list one keyword.

How to set up script hash reporting

Script hash reports are delivered through the Reporting API, so they need report-to and a Reporting-Endpoints header naming an HTTPS endpoint. The older report-uri directive can’t deliver them.

Reporting-Endpoints: csp-endpoint="https://your-endpoint.example/"
Content-Security-Policy-Report-Only: script-src 'self' https://cdn.example.com 'report-sha256'; report-to csp-endpoint

The browser sends hash reports whether the policy is enforced or report-only, so you can start with Content-Security-Policy-Report-Only and see which scripts run before you tighten anything.

Cross-origin scripts need CORS

For a script served from another origin, the browser can only hash it if it was fetched with CORS. Add crossorigin="anonymous" to the script tag, and check that the server hosting the script sends an Access-Control-Allow-Origin header. A CORS request to a server that doesn’t send one fails, and the script won’t load. Without CORS, the browser still reports the script, but with an empty hash.

<script src="https://cdn.example.com/app.js" crossorigin="anonymous"></script>

What a csp-hash report looks like

Reports arrive as a JSON array in the standard Reporting API format, sent with Content-Type: application/reports+json. Each entry has a type of csp-hash, the page url, the user_agent, the report’s age, and a body that identifies the script and carries its hash.

{
  "type": "csp-hash",
  "age": 0,
  "url": "https://example.com/page",
  "user_agent": "Mozilla/5.0 (…) Chrome/…",
  "body": {
    "documentURL": "https://example.com/page",
    "subresourceURL": "https://cdn.example.com/script.js",
    "hash": "sha256-<base64 digest of the script>",
    "destination": "script",
    "type": "subresource"
  }
}

The body has a type of its own, subresource, alongside the envelope’s csp-hash. The hash value uses the same <algorithm>-<base64> format as a hash source, so you can compare it directly with the hashes in your policy or your build output. Hash reports only go to your reporting endpoint. A ReportingObserver on the page never sees them.

Browser support

Chrome and Edge support the report-sha keywords, from Chrome 133 in February 2025. Support for Safari is being developed in WebKit. Firefox doesn’t support them.

What script hash reports don’t include

Hash reports only cover scripts the browser fetches. A script served from the browser’s cache isn’t fetched, so it isn’t reported, and a site with long cache lifetimes sees fewer hash reports than it has scripts. Inline scripts written into the page aren’t fetched either. They show up in violation reports instead, when your policy blocks them.

Script hash reports and PCI DSS 4.0

PCI DSS 4.0 requirement 6.4.3 asks for an inventory of the scripts on payment pages, and 11.6.1 asks for a way to detect unauthorized changes to those pages. Hash reports give you evidence for both: a list of the scripts that ran, and a hash that changes whenever a script’s contents do. Match those hashes against the files you expect to ship to tell an update you made from one you didn’t.

The PCI DSS page covers how HeaderHawk maps to each requirement.

How HeaderHawk handles script hash reports

HeaderHawk records the hash and its algorithm, the script’s URL and host, and the page it ran on. On the Team plan and above, the Scripts view groups them by origin and by script, and on the Professional plan and above by page too.

New hashes skip sampling. The first time a hash is reported for your site, HeaderHawk records it, and repeat reports of a hash it already knows are sampled at the site’s rate, 10% by default. The result is an inventory of the scripts your Chrome and Edge visitors ran, and a script whose contents change shows up as a new hash against a URL you already know.

On the Professional plan and above, the scripts on the pages you mark as payment pages can each be authorized or rejected, with a written justification kept alongside who decided and when. A script new to the site that runs on one of those pages raises an alert, and so does a new hash for a script authorized with pinned hashes, once browsers on two different networks have reported it. The daily scan of those pages closes the gaps this post describes for the scripts their HTML names: it fetches each one, cached or not and with or without CORS, hashes it the same way the browser does, and alerts on what it finds without waiting for a second network. It runs no JavaScript, so a script another script adds is still only as visible as the reports make it.

Setup instructions for your site are in the script integrity docs.

Read your own reports this way

Point your policy's reporting at HeaderHawk and see violations grouped by directive, blocked source and page. The Free plan covers 1 site.