Script Integrity Monitoring
Violation reports tell you what your policy would block. Script integrity reports tell you what genuinely ran: for every external script the browser fetched and executed, its URL, the origin that served it, the page it ran on, and a SHA-256 hash of its contents. When a script’s code changes, its hash changes, and the same URL starts appearing under a second hash.
This is the signal behind the Scripts section of the dashboard, and the one teams reach for when they need an inventory of the JavaScript on a payment page — see CSP reporting and PCI DSS 4.0 for how it maps to requirements 6.4.3 and 11.6.1.
Turning it on
Section titled “Turning it on”Two headers have to agree. Add the 'report-sha256' keyword to your
script-src directive, and declare the endpoint with Reporting-Endpoints and
report-to:
Reporting-Endpoints: csp-endpoint="https://ingest.headerhawk.com/csp/YOUR_INGEST_CODE"Content-Security-Policy-Report-Only: script-src 'self' https://cdn.example.com 'report-sha256'; report-to csp-endpointYour ingest code is on the site’s setup panel, under Settings → Sites in the dashboard.
Two details here produce a policy that looks correct and reports nothing:
report-uricannot deliver a hash report. A hash report is a Reporting API report type.report-uripredates report types and carries CSP violation reports only, so it has no way to carry this one. UseReporting-Endpointswithreport-to, as above. You can keep areport-urialongside it for violation reports in older browsers.- The quotes matter.
'report-sha256'is a keyword source expression and must be quoted and placed insidescript-src. Barereport-sha256is parsed as a hostname and does nothing.
Report-only mode is enough. Hash reporting does not depend on the policy being enforced, so you can collect an inventory without any risk of breaking the page.
What arrives, and what does not
Section titled “What arrives, and what does not”Browser support is narrow
Section titled “Browser support is narrow”Chromium 133 and later implement 'report-sha256'. WebKit support is in
progress. Firefox does not implement it. Whatever share of your traffic is not
recent Chromium contributes nothing.
The feature is also new enough that behaviour varies between builds. Our own testing has met Chromium versions that accept the keyword, raise no error, and emit no hash reports at all — verified against Chrome 152 on our deployed test site in September 2026, while violation reports from the same page arrived normally. Confirm what your visitors’ browsers actually do before relying on it.
A cached script produces no hash
Section titled “A cached script produces no hash”The browser computes the hash when it fetches and executes a script. A script served from cache is never fetched, so no hash is generated, however correct your policy is.
The practical effects:
- A well-cached page reports far fewer hashes than it has scripts.
- Returning visitors contribute almost none.
- On a normal reload during testing you will usually see nothing. A hard refresh (shift+reload) forces the fetch and removes caching as an explanation — though it is not a guarantee a report will follow, since support is still narrow.
Inline scripts never appear
Section titled “Inline scripts never appear”Hash reports describe subresources the browser fetched. A script written inline
in the page is not a fetch, so it does not appear in the Scripts views at all.
Report-only violation reporting still surfaces inline scripts, with a snippet of
their content when your policy includes 'report-sample'.
Repeat sightings are sampled
Section titled “Repeat sightings are sampled”The first time your site reports a given hash, that report skips sampling. After that, repeat reports of the same hash are sampled at the site’s script hash sample rate, which defaults to 10%. Setting that rate to 0% is the exception: it turns hash collection off for the site altogether, first sightings included.
That first-sighting pass is a priority, not a guarantee. Two limits still apply after it, and either can hold a genuinely new hash back until the next sighting:
- your tenant’s monthly report quota. A report is a report: each hash report that gets through counts as one against the same quota your violation reports draw on, and a tenant already over its monthly limit stops receiving hashes; and
- a daily per-site ceiling on script hash events, set at a thirtieth of your plan’s hard limit — 1,000 a day per site on Free, 10,000 on Team. It is an anti-flood guard, not a hash allowance: it bounds how fast reports can arrive, while the monthly quota bounds how many. It sits far above what a real site produces — a page has tens to hundreds of distinct scripts, not thousands a day.
Reports dropped before that point cost you nothing. A hash dropped by sampling,
by the daily ceiling, or because its documentURL does not match the site’s
domain is never counted.
So treat counts as a sample. Read them as “this script was seen”, not as “this script was loaded exactly N times”.
Choosing a sample rate
Section titled “Choosing a sample rate”The sample rate is per site, on a 0–100% slider under Settings → Sites, on the edit dialog for the site.
| Rate | When it fits |
|---|---|
| 10% (default) | Ongoing monitoring. First sightings still take priority; you give up precision on repeat counts, which is the number you should trust least anyway. |
| Higher | A low-traffic site, or a short investigation where you want repeat sightings to show up promptly. More repeat sightings get through, so more of your monthly report quota goes on hashes; the daily per-site ceiling still applies too, so a busy site at a high rate can reach it. |
| 0% | Off. No hash reports are recorded for the site — repeat sightings and first sightings alike — and none count against your quota. Use it on a site where you do not want a script inventory kept at all. |
Raising the rate does not widen coverage — it does not help with cached scripts, unsupported browsers, or inline scripts. It only changes how many repeat sightings of an already-known hash get through, and each one that does counts against your monthly report quota. On a busy site that is the setting to watch: the usage meter in the sidebar, and the fuller breakdown under Settings → Billing, both include hash reports.
There are two ways to stop hash collection, and they act in different places.
Setting the rate to 0% stops Header Hawk recording the reports; the browser
still sends them, and they are discarded on arrival. Removing 'report-sha256'
from your policy stops the browser sending them at all, which is the one to
reach for if the traffic itself is what you want to be rid of.
The Scripts page shows the site’s current rate above the results, so a view that looks sparse can be checked against its own configuration.
Reading the dashboard
Section titled “Reading the dashboard”The Scripts page offers 24-hour, 7-day and 30-day windows only. Shorter windows are not useful here: hashes arrive slowly by construction.
Four summary cards sit above the tabs:
| Card | What it counts |
|---|---|
| Total Reports | Script hash reports received in the window. |
| Unique Origins | Distinct hostnames serving scripts. |
| Unique Scripts | Distinct script URLs, across all pages. |
| Unique Hashes | Distinct SHA-256 content hashes. |
To spot a change, compare hashes per URL in the Scripts view: the same URL under two hashes means that script’s contents changed during the window — a release, a vendor pushing a new build behind a stable URL, or something you did not expect.
Comparing the Unique Hashes and Unique Scripts cards is a weaker signal and can
mislead in both directions. Script URLs are recorded whole, so a cache-busted
app.js?v=2 counts as a second script rather than a second hash; and two URLs
serving identical bytes share one hash. Use the cards for scale, and the
per-URL check for change.
Origins
Section titled “Origins”Every hostname serving scripts to your pages, with how many distinct scripts and reports came from each, and when it was last seen.
Origins are recorded as the bare hostname, so scheme and port are not part of
the grouping: https://cdn.example.com/a.js and http://cdn.example.com:8443/a.js
are one row. If you need to know a script arrived over plain http, the Scripts
view keeps the full URL.
Start here. The list is short enough to read in full, and an origin you do not recognise is the fastest way to find a script you did not know about. A tag manager that loads three more tags from three more vendors shows up as three extra origins.
Scripts
Section titled “Scripts”Every distinct script URL, with its hash, its origin, how many pages it was seen on, its report count, and when it was last seen. Selecting a row opens a drawer with the full URL and full hash.
This is the inventory view. When you are chasing a change, look for the same URL appearing twice with different hashes.
The same data grouped by page URL: which of your pages loaded scripts, how many distinct scripts each one loaded, and when. Use it to answer “what runs on the checkout page” rather than “where does this script run”.
Plan availability
Section titled “Plan availability”The Scripts section appears in the dashboard on Team and above; the Pages view on Professional and above. On the free plan there is no Scripts item in the navigation at all.
This gating is in the dashboard’s navigation and tabs. Collection itself is not plan-gated — a site sending hash reports on any plan is still subject to the sample rate, the monthly quota, and the daily ceiling described above.
What this does not do
Section titled “What this does not do”Two more boundaries worth stating plainly:
- Nothing is scanned on a schedule. Every row comes from a real browser loading a real page. A page with no traffic in a given week produces no rows that week, however it is configured.
- HeaderHawk does not authorize scripts. It can tell you a script is present, and that its contents changed. It cannot tell you whether anyone approved it.
When nothing appears
Section titled “When nothing appears”An empty Scripts page is not by itself evidence of a misconfiguration — see the caveats above, particularly caching and browser support. If you have ruled those out, work through Script Hash Reports Not Appearing in the troubleshooting guide, which also covers the causes that stop reports of every kind, such as ad blockers and domain mismatches.