Beyond CSP: the other reports browsers send, and how to sample them
Browsers report broken security policies, deprecated APIs, failed requests and crashes through the same API as CSP. What each type tells you, and how to keep the noisy ones affordable.
Michael Cameron5 min readUpdated
A Content Security Policy report is one type in a larger system. The Reporting
API that carries csp-violation reports also carries reports about the other
security policies a page sets, about browser features that are going away, and
about requests that failed before your server ever answered. HeaderHawk now
collects all of them on every plan, the free one included, through the same
endpoint your CSP already reports to.
Three kinds of report
Security policies are the closest relatives of CSP. Integrity Policy
reports a script loaded without an integrity attribute. Cross-Origin Embedder
and Opener Policy report the resources and windows that would break under
cross-origin isolation. Permissions Policy reports a page or frame reaching for
a feature you denied, such as the camera, and Document Policy reports a page
using a feature your own policy turns off. Each has a
report-only form, which makes them what a CSP rollout already is: set the
policy, read what it would have broken, then enforce. In HeaderHawk they land
in the same Issues list and Reports Explorer as your CSP violations, grouped
the same way, and a new issue alerts through the same rules.
Browser changes are deprecation and intervention reports. A deprecation report names an API your page called that the browser plans to remove, often with the date it expects to remove it. An intervention report says the browser refused or changed something on the page’s behalf, such as blocking an autoplaying video. These are the reports that tell you, months ahead, that a page will break in a future browser release. They get a page of their own with the soonest removal first, and on Mondays the digest lists every deprecated feature your pages still use that is due to go within 60 days.
Reliability is Network Error Logging and crash reports. NEL reports a request to your origin that failed before a response arrived, with the phase it failed in: DNS, TCP, TLS or the protocol itself. Those are the failures your server logs cannot show, because the request never reached the server. Crash reports say a tab crashed, ran out of memory or stopped responding. Of the major browsers, only Chrome and Edge send either, and both are on a Reliability page by error type, phase and origin, with an alert when a site’s network errors or crashes pass 20 in 15 minutes and five times their usual rate.
Asking for them
A browser sends a report type only when a response header asks for it, and
each type has its own header. Most name the endpoint through
Reporting-Endpoints, the way report-to does in a CSP:
Reporting-Endpoints: headerhawk="https://your-ingest-code.ingest.us.headerhawk.com/r"
Integrity-Policy-Report-Only: blocked-destinations=(script), endpoints=(headerhawk)
Cross-Origin-Opener-Policy-Report-Only: same-origin; report-to="headerhawk"
Two types break the pattern. Deprecation, intervention and crash reports have
no header of their own: Chrome sends them to the endpoint named default, so
asking for them means naming your endpoint default. (Chrome sends a crash to
an endpoint named crash-reporting instead, if the page declares one.) And NEL predates
Reporting-Endpoints, so it still names its endpoint through the older
Report-To header, and the browser keeps the policy for as long as its
max_age says, whether or not you still send it.
None of this needs typing by hand. A site’s setup in HeaderHawk is a checklist
of the report types with a switch for each, and the header block beside it
asks for exactly the types switched on. Switch one off and its header leaves
the block, so the browser stops sending reports nobody reads. Document Policy
is the one type it writes no line for. Which features to turn off is your
site’s decision, so your own Document-Policy names the endpoint instead.
Why each type has its own sample rate
Report types differ wildly in volume. A CSP violation fires when something is wrong with your policy. A deprecation report fires every time a page calls the deprecated API, which for a library loaded on every page is every page load. Collected at full rate, one old analytics snippet calling a deprecated API can outnumber every other report your site sends.
So each type has its own sample rate as well as its own switch. Deprecations and interventions start at 5%, script hashes at 10%, and everything else at 100%. At 5% HeaderHawk keeps about one report in twenty and discards the rest before they count towards your quota, as it does every report of a type you have switched off. The kept reports carry the rate they were kept at, and counts are scaled back up by it, so a deprecation page still shows roughly how often each API is called, marked as an estimate. Raise the rate for a type when you want every report, and lower it when a type is spending more of your quota than it earns.
NEL is the exception, and the cheapest of all. Its header carries a
failure_fraction, and the browser applies it before sending, so the site’s
rate is written into the header and a report the browser never sends costs
nothing.
Where to start
Add the security policy headers first. Their headers are report-only, their
reports sit in the list you already triage, and each one is a policy you were
going to enforce eventually. COEP is the
exception, and starts off. It protects nothing on its own: it is half of
cross-origin isolation, which only a site using SharedArrayBuffer or
WebAssembly threads needs. Its report-only header still reports every
cross-origin image or font that has not opted in, a README badge or an avatar,
on every page view for as long as it stays. Then leave browser changes at 5% for a week and read what
comes back: the deprecations list is short on most sites, and the entries on
it are the ones worth fixing before a browser release fixes them for you.
The docs list every report type and the header that asks for it.