How to parse NEL (Network Error Logging) errors reported through Report-To header endpoint?

Tags: , , ,



I am using the library Network Error Logging to add NEL header and report-to to add Report-To header on my Express.js server.

My code is like

app.use(reportTo({
  groups: [
    {
      group: 'default',
      max_age: 31536000,
      include_subdomains: true,
      endpoints: [
        {
          url: myReportToURL,
          priority: 1,
        },
      ],
    },
  ],
}));
app.use(NEL({
  report_to: 'default',
  max_age: 31536000,
  include_subdomains: true,
}));

More info about them can be found at

I was using use https://report-uri.com to collect NEL errors before and it works well.

It collected different kinds of NEL errors as the screenshot shows:

enter image description here

However, now I want to build my own error parser and then collect.

I got insight from CSP error parser, according to this answer I can parse by

bodyParser.json({ type: 'application/json' });
bodyParser.json({ type: 'application/csp-report' });

For NEL, I found this https://w3c.github.io/reporting/#media-type-registration

So should I parse like this?

bodyParser.json({ type: 'application/reports+json' });

or does it mean?

bodyParser.json({ type: 'application/json' });
bodyParser.json({ type: 'application/reports' });

And if anyone knows how to trigger a NEL error locally, it would be very helpful for testing too. Thanks!


UPDATE 1 (10/14/2020)

Found one W3C example is using application/reports+json.

Another W3C example is using application/report (Note no s).

So I created a ticket asking at W3C GitHub at here.


UPDATE 2 (10/14/2020)

I have pull requested to fix the W3C doc issue. The right format would be application/reports+json.

Regarding how to trigger an NEL error locally. I got a suggestion from Express.js NEL and Report-To libraries author James (@Cherry) at here on GitHub. So I tried to connect https://fakedomainabccba.com to get a dns.name_not_resolved or similar DNS error.

enter image description here

enter image description here

However, Chrome 88.0.4291.0 didn’t not send a NEL error and not shown in the network tab.


UPDATE 3 (10/16/2020)

This is my latest code. I tried to log to both Report URI and my own server by using two endpoints to compare. Report URI did receive new reports, however, my server still has issue.

(The website and API domain are same, so I shouldn’t have CORS issue. And if it is blocked by CORS, I can see it in the log.)

app.use(reportTo({
  groups: [
    {
      group: 'default',
      max_age: 31536000,
      include_subdomains: true,
      endpoints: [
        {
          url: 'https://xxx.report-uri.com/xxx', // Report URI still works
          priority: 1,
        },
        {
          url: 'https://www.example.com/api/report-to', // "example" is my domain currently still has issue
          priority: 1,
        },
      ],
    },
  ],
}));
app.use(NEL({
  report_to: 'default',
  max_age: 31536000,
  include_subdomains: true,
}));

router.post('/api/report-to', bodyParser.json({ type: 'application/reports+json' }), (req, res) => {
  console.log('reportTo', req.body);
  res.sendStatus(200);
});

UPDATE 4 (Working solution, 10/28/2020)

Thanks for @IanClelland help! It works now after I removing the misused internal port in my URL. Also confirmed, as Ian mentioned

The Reporting API will only deliver to a single endpoint, to minimize outgoing bandwidth, and so that people who use multiple endpoints for redundancy don’t double-count reports.

So the final working version looks like

app.use(reportTo({
  groups: [
    {
      group: 'default',
      max_age: 31536000,
      include_subdomains: true,
      endpoints: [
        {
          url: 'https://www.example.com/api/report-to',
          priority: 1,
        },
      ],
    },
  ],
}));
app.use(NEL({
  report_to: 'default',
  max_age: 31536000,
  include_subdomains: true,
}));

router.post('/api/report-to', bodyParser.json({ type: 'application/reports+json' }), (req, res) => {
  console.log('reportTo', req.body);
  res.sendStatus(200);
});

A successful log I received looks like

{
  "age":42187,
  "body":{
    "elapsed_time":674,
    "method":"GET",
    "phase":"application",
    "protocol":"h2",
    "referrer":"",
    "sampling_fraction":1,
    "server_ip":"2606:4700:3032::681b:b258",
    "status_code":404,
    "type":"http.error"
  },
  "type":"network-error",
  "url":"https://www.example.com/undefined",
  "user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4305.0 Safari/537.36"
}

Answer

If you got https://report-uri.com working, then you’re probably most of the way there.

It’s hard to say exactly what’s not working for you right now, but a couple of points that might help:

  • application/reports+json is the correct content type; the explainer is incorrect. (And thanks, I will fix it.)
  • Both the site which triggers the report, and the reporting endpoint need to be on HTTPS; Chrome will remove any insecure endpoints from its cache.
  • If the reporting endpoint is on a different origin from your site, then it will need to support CORS: You’ll need to handle a CORS preflight request to allow the request to proceed.
  • You won’t see the reports in the Devtools network tab; Chrome queues those separately in the browser process, and sends them later, outside of any particular browser tab. They are usually sent within a minute, but if sending fails, Chrome will retry a few times before giving up. You can see the network exchange if you export the network logs from chrome://net-export/, and then view them at https://netlog-viewer.appspot.com/


Source: stackoverflow