# How CSP report-sha keywords reveal every script that loads (/en/blog/csp-report-sha-keywords)





You can ask the browser to tell you the cryptographic hash of every script that loads on a page. The Content Security Policy (CSP) keywords `'report-sha256'`, `'report-sha384'`, and `'report-sha512'` do exactly that: add one to a script directive, point the policy at a reporting endpoint, and each script load produces a report carrying the script's digest. No script is blocked. You get a precise, ongoing record of what runs client-side, which is the raw material for a script inventory.

This is a recent, [Chromium](https://www.chromium.org/Home/)-led addition ([Chrome](https://developer.chrome.com/docs) and other Chromium browsers), with [WebKit](https://webkit.org/) work in progress and no [Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Firefox) support yet. It is not on [MDN](https://developer.mozilla.org/) at the time of writing, so treat it as an emerging feature, not a stable cross-browser one.

## What the report-sha keywords do [#what-the-report-sha-keywords-do]

The keywords are source values you place inside a script directive such as [`script-src`](/en/docs/web-security/policies/content-security-policy/directives/script-src) or [`script-src-elem`](/en/docs/web-security/policies/content-security-policy/directives/script-src-elem), alongside the other [CSP keywords](/en/docs/web-security/policies/content-security-policy/values/csp-keywords) you already use:

* `'report-sha256'` reports each script with a SHA-256 digest
* `'report-sha384'` reports each script with a SHA-384 digest
* `'report-sha512'` reports each script with a SHA-512 digest

They do not allow or block anything. A digest in a report is observability only, separate from the enforcing [hash sources](/en/docs/web-security/policies/content-security-policy/values/csp-hashes-nonce) (the `'sha256-...'` form) you use to permit a specific inline or external script; see [how to generate a CSP hash](/en/blog/csp-hash-sha256) for producing those. When one of these report-sha keywords is present and a script-like resource is fetched, the browser computes the digest of the response and sends a report to the policy's endpoint.

The point is inventory. CSP usually tells you what got blocked. These keywords tell you what loaded, with a digest you can match against known files, so a backend can build a script inventory or software bill of materials (SBOM) and notice when a script changes.

## Set it up [#set-it-up]

Reporting goes through the [`report-to`](/en/docs/web-security/policies/content-security-policy/directives/report-to) directive, which names a group declared in the [`Reporting-Endpoints`](/en/docs/web-security/reporting-api/headers/reporting-endpoints) header. The older [`report-uri`](/en/docs/web-security/policies/content-security-policy/directives/report-uri) directive is not used by this feature, and the [difference between report-uri and report-to](/en/blog/report-uri-vs-report-to) covers why. If you have not wired up reporting yet, walk through [how to set up the browser Reporting API](/en/blog/how-to-set-up-the-reporting-api) first.

Here is the minimal working setup. Declare an endpoint group, then add the keyword and point the policy at the group:

```http
Reporting-Endpoints: hashes-endpoint="https://<Endpoint-ID>.report.centralcsp.com"
```

```http
Content-Security-Policy: script-src 'self' 'report-sha256';
                         report-to hashes-endpoint
```

The endpoint must be HTTPS. Like every Reporting API endpoint, a non-secure URL is ignored.

To collect digests without any risk of breaking the page, start in report-only mode with the [`Content-Security-Policy-Report-Only`](/en/docs/web-security/policies/content-security-policy/report-only) header. You gather the real script inventory first, then tighten the enforcing policy once you know what loads. The same staged approach is covered in [how to build a strong CSP](/en/blog/how-to-build-a-strong-csp) and [getting started with CSP reporting](/en/blog/get-started-csp-reporting).

## What the report looks like [#what-the-report-looks-like]

Reports arrive in the standard Reporting API envelope: a JSON array sent with `Content-Type: application/reports+json`. The outer report `type` for this feature is `"csp-hash"`.

The report body carries the script's identity and digest. Based on the CSP Level 3 spec, the body has these fields:

```json
{
  "documentURL": "https://mywebsite.com/",
  "subresourceURL": "https://mywebsite.com/my_script.js",
  "hash": "sha256-r2hRGID3tnFVlAI+bMCPMjaKx/ovuqgaMic09dPqVCw=",
  "destination": "script",
  "type": "subresource"
}
```

Two `type` values sit at different levels, so keep them apart. The outer Reporting API report `type` is `"csp-hash"`. The inner body has its own `type`, shown as `"subresource"` in the spec example. The `hash` field is formatted as `<algorithm>-<base64>`, the same shape you would use as a hash source in a policy.

Chromium emits the body in camelCase exactly as shown above (`documentURL`, `subresourceURL`, `hash`, `type`, `destination`), wrapped in the standard Reporting API envelope that adds `type`, `url`, `user_agent`, and `age` around it. The CSP Level 3 spec example serializes the body in snake\_case (`document_url`, `subresource_url`), but the shipped Chromium form is camelCase.

## Behavior and gotchas [#behavior-and-gotchas]

A few things are worth knowing before you rely on it.

* **Endpoint only, not in-page.** `csp-hash` reports are not delivered to an in-page `ReportingObserver`; they go only to your server endpoint. This differs from [regular CSP violation reports](/en/blog/csp-violation-report-fields), which Chromium does route to a `ReportingObserver`.
* **Report-only behavior.** The report-sha keywords collect hashes for reporting regardless of whether the surrounding policy is enforced or report-only, since the algorithm does not gate on enforcement.
* **Cross-origin scripts need CORS.** For a script loaded from another origin, the browser can compute and include the digest only when the request was made in CORS mode. Add `crossorigin="anonymous"` to the tag so the browser can read the response body. Without it, the hash for that external script may be missing.

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

* **Combining algorithms.** Whether all three keywords can be set at once to get multiple digests per script, and how a report-sha keyword interacts with an enforcing hash source in the same directive, is not spelled out in the spec, so test it in your own setup before relying on it.

Do not confuse these reporting keywords with the separate `'url-sha256-...'` and `'eval-sha256-...'` enforcement keywords. Those allow or block scripts by URL or eval digest. The `report-sha` keywords only report.

## Why this matters for a script inventory [#why-this-matters-for-a-script-inventory]

Once the browser reports a digest for every script that loads, a backend can do the work that raw violation counts never could: list each script that ran, match digests and URLs against known files and versions, and flag a change the moment a digest you have not seen before appears.

That is the basis of CentralCSP's [script inventory](/platform/supply-chain). We ingest these `csp-hash` reports, build the inventory of what runs on each page, correlate scripts against CVE data, and alert when an unexpected script or hash shows up. For payment pages, that same script-change visibility maps directly to PCI DSS v4 requirements 6.4.3 and 11.6.1, where the platform helps you meet the client-side script-management and tamper-detection controls (your QSA signs off, not us).

If you want the digests collected and turned into an inventory without standing up your own pipeline, [start free](/register) and point your `Reporting-Endpoints` header at your CentralCSP endpoint.

<img alt="Scripts grouped by origin, each with the hash the browser reported" src="__img0" width="1359" height="483" />

## Quick recap [#quick-recap]

Add a `report-sha` keyword to a script directive, declare a `Reporting-Endpoints` group, and reference it with `report-to`. The browser sends a `csp-hash` report carrying each script's digest, nothing is blocked, and you get the per-script visibility that drives a script inventory. It is Chromium-first today, so plan for partial browser coverage and gather digests in report-only mode before you lean on them.

## Related [#related]

* [report-sha256 keyword reference](/en/docs/web-security/policies/content-security-policy/values/report-sha-keyword)
* [Build a script inventory with CSP hash reporting](/en/blog/script-inventory)
* [Get started with CSP reporting](/en/blog/get-started-csp-reporting)
