From f09d01b8f097af59bb636fe5911c5bea9b3e1fbb Mon Sep 17 00:00:00 2001 From: Naor Peled Date: Sat, 26 Oct 2024 15:51:07 +0300 Subject: [PATCH] feat(http-security-headers): support report only mode --- .../http-security-headers/__tests__/index.js | 23 +++++++++++++++++++ packages/http-security-headers/index.d.ts | 4 +++- packages/http-security-headers/index.js | 10 ++++++-- .../http-security-headers/index.test-d.ts | 3 ++- 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/packages/http-security-headers/__tests__/index.js b/packages/http-security-headers/__tests__/index.js index b64742209..a739d4bed 100644 --- a/packages/http-security-headers/__tests__/index.js +++ b/packages/http-security-headers/__tests__/index.js @@ -268,3 +268,26 @@ test('It should apply security headers if error is handled', async (t) => { equal(response.headers['X-Frame-Options'], undefined) equal(response.headers['X-XSS-Protection'], undefined) }) + +test('It should support report only mode', async (t) => { + const handler = middy(() => createHtmlObjectResponse()) + + handler.use( + httpSecurityHeaders({ + reportOnly: true + }) + ) + + const event = { + httpMethod: 'GET' + } + + const response = await handler(event, defaultContext) + + equal(response.statusCode, 200) + equal(response.headers['Content-Security-Policy'], undefined) + equal( + response.headers['Content-Security-Policy-Report-Only'], + "default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; navigate-to 'none'; report-to csp; require-trusted-types-for 'script'; trusted-types 'none'; sandbox; upgrade-insecure-requests" + ) +}) diff --git a/packages/http-security-headers/index.d.ts b/packages/http-security-headers/index.d.ts index 003148b41..8646aeb11 100644 --- a/packages/http-security-headers/index.d.ts +++ b/packages/http-security-headers/index.d.ts @@ -28,7 +28,9 @@ interface Options { xssProtection?: { reportUri?: string } - contentSecurityPolicy?: Record + contentSecurityPolicy?: Record & { + reportOnly?: boolean + } crossOriginEmbedderPolicy?: { policy?: string } diff --git a/packages/http-security-headers/index.js b/packages/http-security-headers/index.js index e86af3b9e..6f4ffe52c 100644 --- a/packages/http-security-headers/index.js +++ b/packages/http-security-headers/index.js @@ -34,7 +34,8 @@ const defaults = { // Other directives 'require-trusted-types-for': "'script'", 'trusted-types': "'none'", - 'upgrade-insecure-requests': '' + 'upgrade-insecure-requests': '', + reportOnly: false }, contentTypeOptions: { action: 'nosniff' @@ -138,6 +139,7 @@ const helmetHtmlOnly = {} // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy helmetHtmlOnly.contentSecurityPolicy = (headers, config) => { let header = Object.keys(config) + .filter((policy) => policy !== 'reportOnly') .map((policy) => (config[policy] ? `${policy} ${config[policy]}` : '')) .filter((str) => str) .join('; ') @@ -147,7 +149,11 @@ helmetHtmlOnly.contentSecurityPolicy = (headers, config) => { if (config['upgrade-insecure-requests'] === '') { header += '; upgrade-insecure-requests' } - headers['Content-Security-Policy'] = header + + const cspHeaderName = config.reportOnly + ? 'Content-Security-Policy-Report-Only' + : 'Content-Security-Policy' + headers[cspHeaderName] = header } // crossdomain - N/A - for Adobe products helmetHtmlOnly.crossOriginEmbedderPolicy = (headers, config) => { diff --git a/packages/http-security-headers/index.test-d.ts b/packages/http-security-headers/index.test-d.ts index 624422d2c..f49291b0f 100644 --- a/packages/http-security-headers/index.test-d.ts +++ b/packages/http-security-headers/index.test-d.ts @@ -37,7 +37,8 @@ middleware = httpSecurityHeaders({ }, contentSecurityPolicy: { 'default-src': "'none'", - sandbox: '' + sandbox: '', + reportOnly: true }, crossOriginEmbedderPolicy: { policy: 'require-corp'