# Build a script inventory with CSP hash reporting (/en/blog/script-inventory)





You cannot secure what you cannot see. A typical site loads dozens of scripts: analytics, tag managers, chat widgets, A/B testing, payment SDKs, and your own bundles. Each one runs in your users' browsers with full access to the page. If one of them is swapped or tampered with, you want to know before your customers do.

A script inventory is a structured list of every script the browser loads and runs on a page, first-party and third-party. The modern way to build one is to let the browser report it for you. Content Security Policy (CSP) Level 3 adds hash reporting: the browser computes a cryptographic hash of each script it fetches and sends it to a reporting endpoint. You collect those reports and turn them into a living inventory, a software bill of materials (SBOM) for your client-side code.

This post shows how CSP hash reporting works, what the reports contain, and how the inventory maps to the two PCI DSS v4 requirements that now ask for it.

## Why a client-side script inventory matters [#why-a-client-side-script-inventory-matters]

A server-side dependency scan tells you what is in your build. It says nothing about what actually executes in the browser, where third-party tags inject more scripts at runtime and an attacker can replace a file on a CDN you do not control.

The browser is the only place that sees the full, real list. With an inventory built from what the browser reports, you can:

* Know exactly which scripts run, from where, and with what hash.
* Spot a new script, a new origin, or a changed hash that may signal a compromised dependency or an injection.
* Show an auditor which scripts run on a payment page and that you watch them for change.

Threats like formjacking and Magecart ([what Magecart and formjacking are](/en/blog/magecart-formjacking-detection)) work by quietly modifying a script that loads on a checkout page, and a trusted tag manager is one common delivery path; see [how attackers abuse Google Tag Manager](/en/blog/google-tag-manager-security-risk). A baseline inventory plus change detection is how you catch that.

## How CSP hash reporting works [#how-csp-hash-reporting-works]

CSP Level 3 introduces three reporting-only source keywords you can place inside a script directive: [`'report-sha256'`](/en/docs/web-security/policies/content-security-policy/values/report-sha-keyword), `'report-sha384'`, and `'report-sha512'`. They do not allow or block anything. They ask the browser to compute the hash of each fetched script-like resource and send a report.

Reports travel over the [Reporting API](/en/docs/web-security/reporting-api). To receive them you declare an endpoint with the `Reporting-Endpoints` header and reference it from a [`report-to`](/en/docs/web-security/policies/content-security-policy/directives/report-to) directive in the same policy, the same wiring covered in [how to set up the Reporting API](/en/blog/how-to-set-up-the-reporting-api). Without `report-to`, the browser emits no hash report.

Here is the minimal working header pair. Point the endpoint at your CentralCSP reporting URL.

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

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

A good way to inventory an existing site without risk is to add the keyword in report-only mode first. The browser reports the hashes of every script that currently loads, and nothing is blocked while you build the baseline.

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

The keyword can live alongside the [`script-src`](/en/docs/web-security/policies/content-security-policy/directives/script-src) directive you already use. It coexists with your allow list and your existing nonces or hashes. The verified behavior is for `script-src`; whether other script-like directives (a `default-src` fallback or `script-src-elem`) also trigger the report is not documented, so rely on `script-src`.

## What the csp-hash report contains [#what-the-csp-hash-report-contains]

Each reported script produces a `csp-hash` report. The body identifies the document, the script URL, and the integrity-style hash so you can match it to a known-good value.

```json
{
  "type": "csp-hash",
  "age": 12,
  "url": "https://example.com/",
  "body": {
    "documentURL": "https://example.com/",
    "subresourceURL": "https://example.com/main.js",
    "hash": "sha256-85738f8f9a7f1b04b5329c590ebcb9e425925c6d0984089c43a022de4f19c281",
    "type": "subresource",
    "destination": "script"
  }
}
```

The `hash` field is the same algorithm-prefixed, base64 format used by Subresource Integrity, so it lines up with the integrity hashes you already manage. Reports arrive as a JSON array POSTed with the `application/reports+json` content type to a secure endpoint.

Two practical limits to know:

* The browser computes a usable hash only for CORS-same-origin responses. A cross-origin third-party script may be reported by URL but without a meaningful hash. You still see that it loaded and from where.
* Browsers emit camelCase field names (`documentURL`, `subresourceURL`, `hash`, `type`, `destination`), which is the form shown above. The CSP3 spec example JSON uses snake\_case (`document_url`, `subresource_url`), so a collector should tolerate both spellings.

Collecting, deduplicating, and tracking these reports by hand is tedious. CentralCSP ingests the `csp-hash` stream, builds the script inventory for you, adds technology, version, and CVE detection, and alerts you when a script or hash changes. You can explore it on the [script inventory feature page](/platform/supply-chain).

## Browser support [#browser-support]

CSP hash reporting for scripts shipped in Chromium-based browsers, including Chrome on Android and WebView. It added the `'report-sha256'`, `'report-sha384'`, and `'report-sha512'` keywords. Firefox and Safari do not support it, so treat it as Chromium-only today.

Do not confuse this with a separate, later Chromium origin trial that prints script URLs and `eval` hashes to DevTools and reports. That is experimental. The `report-sha*` script hash reporting described here is shipped.

<img alt="A script inventory listing scripts with review statuses" src="__img0" width="1359" height="645" />

## What this gives you for PCI DSS v4 [#what-this-gives-you-for-pci-dss-v4]

PCI DSS v4 added two requirements aimed squarely at scripts running in the customer's browser on payment pages. Both moved from best practice to required after 31 March 2025. For how a CSP maps to these, see [CSP for PCI DSS v4](/en/blog/csp-pci-dss-v4).

**Requirement 6.4.3** asks you to manage every script loaded and executed on a payment page: confirm each script is authorized, assure the integrity of each script, and maintain an inventory of all scripts with written justification for why each is needed.

**Requirement 11.6.1** asks for a change- and tamper-detection mechanism that alerts personnel to unauthorized changes to the HTTP headers and the contents of payment pages as received by the browser, evaluated at least once every seven days or at a frequency set by your targeted risk analysis.

A script inventory built from hash reporting helps you meet both. The hashes are your authorized baseline and your integrity check for 6.4.3, and the per-script inventory is the documented list it calls for. A changed hash or a new script origin is the change signal 11.6.1 wants to detect and alert on. CentralCSP does not certify your PCI DSS compliance; a QSA does that. What it provides is the data, the alerting, and the exportable evidence that support the requirements. The requirement wording above is paraphrased; the official PCI DSS v4.0.1 document is the authoritative text.

## Put it in place [#put-it-in-place]

Start by adding `'report-sha256'` in report-only mode and pointing `report-to` at your endpoint, then review the inventory the browser builds for you before you tighten the enforcing policy. From there you watch for change rather than re-auditing by hand.

If you want the collection, deduplication, CVE detection, and alerting handled for you, [start free with CentralCSP](/register) and let the reports build the inventory.

## Related [#related]

* [CSP report-sha keywords explained](/en/blog/csp-report-sha-keywords)
* [How to build a strong CSP, step by step](/en/blog/how-to-build-a-strong-csp)

## Sources [#sources]

* [PCI Security Standards Council, PCI DSS v4.0.1](https://www.pcisecuritystandards.org/document_library/)
* [W3C, Subresource Integrity](https://www.w3.org/TR/SRI/)
* [MDN, Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity)
* [W3C, Content Security Policy Level 3](https://www.w3.org/TR/CSP3/)
