UNPKG

reporting-api

Version:

Roll your own Reporting API collector. Supports CSP, COEP, COOP, Document-Policy, Crash reports, Deprecation reports, Intervention reports and Network Error Logging

157 lines (153 loc) 6.04 kB
'use strict'; var debug = require('debug'); const log = debug('reporting-api:headers'); /** * Headers that support the Reporting API v1 */ const headers = [ 'Content-Security-Policy', 'Content-Security-Policy-Report-Only', 'Permissions-Policy', 'Permissions-Policy-Report-Only', 'Cross-Origin-Opener-Policy', 'Cross-Origin-Opener-Policy-Report-Only', 'Cross-Origin-Embedder-Policy', 'Cross-Origin-Embedder-Policy-Report-Only', ]; /** * Adds reporting to all existing headers and sets the `Reporting-Endpoints` header * * Headers that support the Reporting API `report-to` directive: * * - Content-Security-Policy * - Content-Security-Policy-Report-Only * - Permissions-Policy * - Permissions-Policy-Report-Only * - Cross-Origin-Opener-Policy * - Cross-Origin-Opener-Policy-Report-Only * - Cross-Origin-Embedder-Policy * - Cross-Origin-Embedder-Policy-Report-Only * * @param reportingUrl The pathname or full URL for the reporting endpoint */ function setupReportingHeaders(reportingUrl, config = {}) { // If a version is set then include it in the endpoint if (config.version) { reportingUrl = addSearchParams(reportingUrl, { version: String(config.version), }); } return (req, res, next) => { let setHeader = false; if (res.getHeader('Reporting-Endpoints')) { log('Reporting-Endpoints already set, will not set up reporting'); if (next) { next(); } return; } // The 'default' reporting group always receives Deprecation, Crash and Intervention reports. // If we do not want to collect those, always use 'reporter' as group name. const reportTo = config.enableDefaultReporters ? 'default' : config.reportingGroup || 'reporter'; for (const headerKey of headers) { const headers = res.getHeader(headerKey); if (!headers) { continue; } const values = Array.isArray(headers) ? headers : [headers]; for (let i = 0; i < values.length; i++) { const value = values[i]; if (typeof value !== 'string') { continue; } const newHeader = addReporterToHeader(headerKey, value, reportTo, reportingUrl); if (newHeader) { values[i] = newHeader; setHeader = true; } } res.setHeader(headerKey, values); } // Only set Reporting-Endpoints if any existing header was modified with reporting if (setHeader) { res.append('Reporting-Endpoints', `${reportTo}="${reportingUrl}"`); } if (config.enableNetworkErrorLogging) { // Reporting API v1 does not support Network Error Logging // so we rely on the Reporting API v0 `Report-To` header // https://developer.chrome.com/blog/reporting-api-migration#network_error_logging res.append('Report-To', JSON.stringify({ group: reportTo, max_age: 60 * 60 * 24, // seconds? endpoints: [{ url: reportingUrl }], })); const nel = { report_to: reportTo, max_age: 60 * 60 * 24, // 1 day }; if (typeof config.enableNetworkErrorLogging === 'object') { nel.failure_fraction = config.enableNetworkErrorLogging.failure_fraction; nel.success_fraction = config.enableNetworkErrorLogging.success_fraction; nel.include_subdomains = config.enableNetworkErrorLogging.include_subdomains; } res.setHeader('NEL', JSON.stringify(nel)); } if (next) { next(); } return; }; } /** * Adds a reporter to a header */ function addReporterToHeader(header, value, reportingGroup, reportingUri) { if (typeof value !== 'string' || value.includes('report-to') || value.includes('report-uri ')) { log(`Header "%s: %s" already contains reporter`, header, value); return null; } switch (header) { case 'Content-Security-Policy': case 'Content-Security-Policy-Report-Only': // report-uri is deprecated in CSP 3 and ignored if the browser supports report-to, but Firefox does not and will use report-uri const reportUri = addSearchParams(reportingUri, { // Older versions of firefox doesn't include the disposition so we track it manually disposition: header === 'Content-Security-Policy' ? 'enforce' : 'report', }); value += `;report-uri ${reportUri}`; // CSP does not have a `=` between report-to and the group name value += `;report-to ${reportingGroup}`; break; case 'Permissions-Policy': case 'Permissions-Policy-Report-Only': // https://github.com/w3c/webappsec-permissions-policy/blob/main/reporting.md value += `;report-to=${reportingGroup}`; break; case 'Cross-Origin-Embedder-Policy': case 'Cross-Origin-Embedder-Policy-Report-Only': case 'Cross-Origin-Opener-Policy': case 'Cross-Origin-Opener-Policy-Report-Only': // All other headers than CSP needs the `=` and needs to be encapsulated with "" value += `;report-to="${reportingGroup}"`; break; default: log(`Unknown header ${header}`); return null; } return value; } function addSearchParams(url, params) { const sep = url.includes('?') ? '&' : '?'; const usp = new URLSearchParams(params); usp.sort(); return `${url}${sep}${usp.toString()}`; } exports.setupReportingHeaders = setupReportingHeaders; //# sourceMappingURL=setup-headers.cjs.map