Tell the browser where to send policy reports

Report To

Preliminaries

A browser can send reports to you when something out-of-the-ordinary happens while visitors access your website.

Browsers send out-of-band reports (when properly configured by you) about problems encountered by visitors to your website. This is an important mechanism, allowing you to monitor the health of your website from the outside looking in. It is the complement to server-side logging and error reporting.

You can subscribe to four types of reports from the browser: 1) security policy violations, 2) network errors, 3) deprecation reports, and 4) intervention reports.

As of this writing (2019), this is an experimental technology, governed by a working draft specification.

You set this up by sending the browser a report-to header — with one or more named report groups — instructing it where to send violation and network error reports. The report-to header should be sent together with a content-security-policy header or a network-error-logging header, which in turn should reference the report group you set up.

Upon receipt, the browser will record and begin to use the specified instructions for all subsequent violations and errors for all documents coming from your website's domain (protocol + hostname + port). The browser will continue to do this, for the period of time that you specify in the max-age portion of the header.

The report-to header specifies an endpoint that is responsible for accepting and processing the reports sent to it by the browser. The endpoint is specified as a URL having a protocol, hostname, and resource path.

The endpoint may be hosted on the same web server as your website, but this is generally not wise. While security violation reports will successfully be reported to such an endpoint, network errors almost certainly will not. To ensure proper delivery of browser reports, it is safer to place the receiving endpoint on a different target hostname.

Following this advice though, leads to a fresh problem. CORS. In order for the browser to successfully send a report about your website to a different hostname, you must configure your website to allow cross origin resource sharing.

In order for the browser to successfully initiate a report from your site, it must receive a valid set of CORS headers. For example, a successful CORS configuration for a report endpoint hosted at https://server-monitor.tld for a website at https://domain.tld will need to respond to the browser's preflight OPTIONS request with these three response headers:

access-control-allow-origin: https://domain.tld
access-control-allow-methods: POST
access-control-request-headers: content-type

When the browser receives CORS permission to proceed, it will send reports using an HTTP POST method with a content type of application/reports+json. The endpoint should accept the POST data, process it, and respond to the browser with status code 204 (No Content).

Configuration

The report-to section is configured subordinate to the policies section. It contains one or more named report groups, each comprising two required settings, and one optional setting.

The required settings are:

  • endpoint which is a URL, plus optional priority and weight attributes.
  • max-age which is the length of time (in seconds) that the browser should keep using this endpoint.

The optional setting is:

  • include-subdomains which will instruct the browser to also send reports to the specified endpoint for any subdomains of your website.

It is also possible to specify more than one endpoint for a single named group. To do this configure the section using an endpoints (plural) subsection, and list the URL, priority, and weight for each one, on a separate line.

Finally, the special default group can also be specified. It will be used to receive browser deprecation reports and intervention reports, which cannot be sent to named groups.

EBNF

SP ::= U+20
CR ::= U+0D
QUOTATION-MARK ::= U+22
APOSTROPHE ::= U+27
ASTERISK ::= U+2A
COMMA ::= U+2C
HYPHEN ::= U+2D
FULL-STOP ::= U+2E
SOLIDUS ::= U+2F
EQUALS-SIGN ::= U+3D
LEFT-CURLY-BRACKET ::= U+7B
RIGHT-CURLY-BRACKET ::= U+7D
parameters
group-name ::= (ALPHA | DIGIT | HYPHEN)*
protocol ::= 'https://' | 'http://'
hostname ::= (ALPHA | DIGIT | FULL-STOP | HYPHEN)*
resource-path ::= (url-safe-characters)*
endpoint-url ::= protocol hostname SOLIDUS resource-path
priority-spec ::= ASTERISK 'priority' EQUALS-SIGN DIGITS*
weight-spec ::= ASTERISK 'weight' EQUALS-SIGN DIGITS*
endpoints
endpoint-spec ::= endpoint-url SP priority-spec SP weight-spec
endpoint ::= 'endpoint' SP endpoint-spec CR
endpoints ::= 'endpoints' SP LEFT-CURLY-BRACKET CR
(endpoint-spec CR)*
RIGHT-CURLY-BRACKET CR
max-age ::= 'max-age' SP DIGIT* CR
include-subdomains ::= 'include-subdomains' CR
report groups
report-group ::= 'default' | group name SP LEFT-CURLY-BRACKET CR endpoint | endpoints | max-age | include-subdomains RIGHT-CURLY-BRACKET CR
policy-configs ::= referrer-policy | content-security-policy | feature-policy | network-error-logging | report-to
policies-section ::= 'policies' SP LEFT-CURLY-BRACKET CR
policy-configs*
RIGHT-CURLY-BRACKET CR

† These are defined in separate notes

Cookbook

Example 1: reporting security violations to origin server
host {
hostname domain.tld
modules {
policies on
}
policies {
content-security-policy {
default-src 'self'
report-to csp-endpoint
}
report-to {
csp-endpoint {
endpoint `https://domain.tld/csp-notifications`
max-age 2592000
}
}
}
plugins {
router {
`/csp-notifications` *methods=POST *plugin=rwserve-policy-reports
}
}
}
Example 2: reporting errors to endpoint on separate domain

The server to be monitored is configured with:

host {
hostname domain.tld
modules {
policies on
}
policies {
network-error-logging {
report-to nel-endpoint
max-age 2592000
}
report-to {
nel-endpoint {
endpoint `https://server-monitor.tld/nel-notifications`
max-age 2592000
}
}
}
}

The server to receive browser-generated error reports is configured with:

host {
hostname server-monitor.tld
modules {
cross-origin on
}
request {
cross-origin {
`/nel-notifications` *origin="domain.tld" *headers=content-type *methods=POST
}
}
plugins {
router {
`/nel-notifications` *methods=POST *plugin=rwserve-policy-reports
}
}
}
Example 3: receiving deprecation and intervention reports
host {
hostname domain.tld
modules {
policies on
}
policies {
report-to {
default {
endpoint `https://domain.tld/default-notifications`
max-age 2592000
}
}
}
plugins {
router {
`/default-notifications` *methods=POST *plugin=rwserve-policy-reports
}
}
}

Review

Key points to remember:

  • The policies module must be on to enable report-to headers.
  • The report-to response header is only sent when an HTML document is requested.
  • The default group is used for deprecation and intervention reports.
  • CORS must be configured when the report-to endpoint is on a different host.

Tell the browser where to send policy reports