diff --git a/packages/config/src/options.js b/packages/config/src/options.js index 8c9157a565..bc3cf2a9e7 100644 --- a/packages/config/src/options.js +++ b/packages/config/src/options.js @@ -220,6 +220,7 @@ export function getNuxtConfig(_options) { allowedSources: undefined, policies: undefined, addMeta: Boolean(options._generate), + unsafeInlineCompatiblity: false, reportOnly: options.debug }) } diff --git a/packages/config/test/options.test.js b/packages/config/test/options.test.js index d89d5ff054..15573d0b94 100644 --- a/packages/config/test/options.test.js +++ b/packages/config/test/options.test.js @@ -92,6 +92,7 @@ describe('config: options', () => { expect(csp).toEqual({ hashAlgorithm: 'sha256', addMeta: false, + unsafeInlineCompatiblity: false, allowedSources: true, policies: undefined, reportOnly: false, diff --git a/packages/vue-renderer/src/renderers/ssr.js b/packages/vue-renderer/src/renderers/ssr.js index 150d2888c0..073df25bda 100644 --- a/packages/vue-renderer/src/renderers/ssr.js +++ b/packages/vue-renderer/src/renderers/ssr.js @@ -121,7 +121,7 @@ export default class SSRRenderer extends BaseRenderer { if (csp) { // Only add the hash if 'unsafe-inline' rule isn't present to avoid conflicts (#5387) const containsUnsafeInlineScriptSrc = csp.policies && csp.policies['script-src'] && csp.policies['script-src'].includes(`'unsafe-inline'`) - if (!containsUnsafeInlineScriptSrc) { + if (csp.unsafeInlineCompatiblity || !containsUnsafeInlineScriptSrc) { const hash = crypto.createHash(csp.hashAlgorithm) hash.update(serializedSession) cspScriptSrcHashes.push(`'${csp.hashAlgorithm}-${hash.digest('base64')}'`) diff --git a/test/unit/basic.ssr.csp.test.js b/test/unit/basic.ssr.csp.test.js index 90d13e89a4..b5fa67685f 100644 --- a/test/unit/basic.ssr.csp.test.js +++ b/test/unit/basic.ssr.csp.test.js @@ -196,7 +196,34 @@ describe('basic ssr csp', () => { expect(headers[cspHeader]).toMatch(/script-src 'self' 'unsafe-inline'$/) } ) + + test( + 'Contain hash and \'unsafe-inline\' when unsafeInlineCompatiblity is enabled', + async () => { + const policies = { + 'script-src': [`'unsafe-inline'`] + } + + nuxt = await startCspServer({ + unsafeInlineCompatiblity: true, + policies + }) + + for (let i = 0; i < 5; i++) { + await rp(url('/stateless'), { + resolveWithFullResponse: true + }) + } + + const { headers } = await rp(url('/stateful'), { + resolveWithFullResponse: true + }) + + expect(headers[cspHeader]).toMatch(/script-src 'sha256-.*' 'self' 'unsafe-inline'$/) + } + ) }) + describe('debug mode', () => { test( 'Not contain Content-Security-Policy-Report-Only header, when csp is false', @@ -390,5 +417,31 @@ describe('basic ssr csp', () => { expect(headers[reportOnlyHeader]).toMatch(/script-src 'self' 'unsafe-inline'$/) } ) + + test( + 'Contain hash and \'unsafe-inline\' when unsafeInlineCompatiblity is enabled', + async () => { + const policies = { + 'script-src': [`'unsafe-inline'`] + } + + nuxt = await startCspServer({ + unsafeInlineCompatiblity: true, + policies + }) + + for (let i = 0; i < 5; i++) { + await rp(url('/stateless'), { + resolveWithFullResponse: true + }) + } + + const { headers } = await rp(url('/stateful'), { + resolveWithFullResponse: true + }) + + expect(headers[cspHeader]).toMatch(/script-src 'sha256-.*' 'self' 'unsafe-inline'$/) + } + ) }) })