Features

CSP violation reporting

Point your policy's reporting directive at HeaderHawk, and every violation a browser sends is kept, grouped into the problem it describes, and opened onto the file and line that caused it. A thousand reports of one blocked script read as one issue, with the pages it affects and the code that asked for it.

Reports from browser extensions, harmless URL schemes and known bots are classified as noise. You can still see them, and they never page anyone on their own.

Plans
Every plan, the Free plan included
In the dashboard
Policy › Issues and Draft, and the Explorer

What's at stake

A report-only policy tells you everything, all at once

The first day of a new policy brings more reports than anyone can read. The job is to turn that pile into a short list someone can fix before lunch.

The firehose

Ten thousand reports, four problems

Every page view that trips a directive sends a report. Read one at a time, a single misconfigured tag looks like a flood, and this morning's new problem is buried in it.

No location

A count is not a place to start

Knowing that script-src refused a host eight hundred times does not say which bundle asked for it. Without the file and line, the next step is searching code somebody else wrote.

Somebody's ad blocker

Extensions report too

A visitor's password manager or ad blocker injects code your policy refuses, and the browser reports it like any other violation. Alert on each one and everyone learns to ignore the alerts.

What it does

From a pile of reports to the line of code

Grouped into the problems they describe

Violations group by the directive that refused, the source it refused and the page it happened on. Each group is an issue whose title says what happened, with its report count, the pages it affects, and when it was first and last seen, so a new problem stands out from an old one.

A vendor's loader asks for a chat widget the policy does not allow:
{
  "csp-report": {
    "document-uri": "https://shop.example.com/account/orders",
    "referrer": "https://shop.example.com/",
    "violated-directive": "script-src-elem",
    "effective-directive": "script-src-elem",
    "original-policy": "default-src 'self'; script-src 'self' https://js.stripe.com; report-uri https://k3x9mp2nq7ab.ingest.us.headerhawk.com/r",
    "disposition": "report",
    "blocked-uri": "https://widgets.example-chat.com/loader.js",
    "source-file": "https://shop.example.com/assets/tag-loader.js",
    "line-number": 212,
    "column-number": 18,
    "status-code": 200,
    "script-sample": ""
  }
}

How it appears in HeaderHawk

script-src-elem blocking widgets.example-chat.com

  • Every report of this refusal, from every page and browser, joins this one issue.
  • source-file, line-number and column-number point at the code that asked, here line 212 of your tag loader, not the chat vendor.
  • The quoted policy is kept too, for policy change detection to read.

Field by field: the script-src-elem reference.

Down to the file, line and sample

Open an issue for the pages it affects, then open a report for the fields the browser sent. They include the source file, line and column of the code that triggered it, the referrer, the user agent, the policy in force, and the script sample where your policy carries 'report-sample'. The Explorer holds every stored report, filterable by site, directive, host and time, for questions a grouped view does not answer.

Noise held out of alerts, not out of sight

A report is classified as noise when its blocked source is a browser-extension URL or an about:, data: or blob: URL, or when its user agent is a known crawler. Noise never raises an alert or makes a real issue look more urgent. It stays in the dashboard for the day you want to know which extensions your customers run.

Alerts are scored as well as filtered. A problem reported by two browser families, or twenty times by one, alerts immediately, and a quieter one waits for the daily or weekly digest.

What is classified as noise
chrome-extension://…/content.js   extension, held back
moz-extension://…/inject.js       extension, held back
about:blank                        harmless scheme, held back
Googlebot, bingbot, AhrefsBot      bot user agent, held back

A policy proposed from your own traffic

A site's first step is discovery, so you never have to guess what your policy should allow: signing up starts it for the site you add, and the Overview offers it for every site after. For a week, your visitors' browsers report every script, style, image, font, frame and connection your pages load, including the pages behind a login, to an endpoint of their own. None of it becomes an issue or an alert.

HeaderHawk then proposes a policy one directive at a time, each source with the browsers, pages and dates behind it. A source two browser families loaded is in. One a single browser reported once is listed and left out until you keep it. Third-party hosts are named from a catalog of known vendors. One the catalog does not know can get a name suggested by AI, marked as a suggestion. The model is sent the host name and the kind of resource it served, nothing else. A host that looks wrong is flagged and left out until you look at it: a host first seen late in the run, seen only on your payment pages, or a letter away from a vendor you already use. Inline scripts and eval are flagged with the code that ran, never allowed. Keep or drop each source, watch the grade change, and copy the result as a report-only header. The Overview then takes the policy from report-only to enforcing, one step at a time, each when your reports say the last has settled.

The first report of each source is always kept and repeats are sampled, and a sampled-out report does not count towards your monthly reports.

A proposal after a week of discovery
default-src      'none'
script-src       'self' https://js.stripe.com
style-src        'self'
img-src          'self' data:
connect-src      https://api.stripe.com
frame-src        https://js.stripe.com
object-src       'none'
base-uri         'none'
form-action      'self'
frame-ancestors  'none'

In:       https://js.stripe.com: Chrome, Firefox, Safari, about 1,240 reports, 6 pages
Left out: https://cdn.example-analytics.net: Chrome, 1 report, 1 page
Flagged:  inline script on /checkout, "window.dataLayer = window.dataLayer ||"
          needs a nonce or a hash

A site's own endpoint, and only its own hosts

Each site gets an endpoint URL you can rotate if it leaks. It accepts reports only from the hosts that site lists, exact hosts or wildcards covering every subdomain, on every plan. A report from anywhere else is dropped, not counted against you. Violation reports are never sampled.

How it runs

The reporting directive is the whole integration.

A site's first step is a week of discovery, which proposes a policy from your own traffic. You can also keep the policy you have and add the endpoint to it, or start from the starter policy in a site's setup. The free report header builder does the same without an account.

  • Both formats are accepted, report-uri's single reports and report-to's Reporting API batches.
  • No script, tag or agent is added to your pages. Browsers post reports to the endpoint directly.
  • Start in report-only mode, which blocks nothing, and fix what it reports before you enforce.
  • HeaderHawk is never in the request path. When you rename the header, the browser enforces the policy.

Scope

One layer, and where it stops

Reporting makes a policy safe to roll out and cheap to keep. The policy is the control, and the reports show whether it is the one you meant.

CSP violation reporting doesIt does not replace
Keeps every violation report a browser sends from your hostsGiving inline code a nonce or a hash. The proposal flags each inline script and style, and your application has to send them
Groups them into issues with pages, counts and datesFixing the injection a violation points at
Opens a report onto the file, line, column and sampleA web application firewall, which sees requests to your servers, not what runs in the browser
Holds extension, bot and harmless-scheme reports out of alertsA check for a policy your pages stop sending, which goes quiet instead of reporting. The daily header scans on the Professional and Business plans cover that
Proposes a policy from your own traffic, with the evidence for each source

Start with a report-only policy

The Free plan covers 1 site with no card. Leave the policy in report-only until the reports go quiet.