From 20b096635eac7a8363ebeabb4958d62626c12ad0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Raffray?= Date: Tue, 17 Oct 2023 19:46:10 +0200 Subject: [PATCH] fix nonce issue - in SSG mode, modify the 99-cspNonce nitro plugin - in SSR mode, modify the headers for prerendered routes --- src/module.ts | 17 ++++++++++++++++- src/runtime/nitro/plugins/99-cspNonce.ts | 11 +++++++---- test/nonce.test.ts | 3 +-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/module.ts b/src/module.ts index 33e61845..4a2e72bb 100644 --- a/src/module.ts +++ b/src/module.ts @@ -84,6 +84,11 @@ export default defineNuxtModule({ setSecurityRouteRules(nuxt, securityOptions) + // Remove Content-Security-Policy header in pre-rendered routes + // When pre-rendered, the CSP is provided via html instead + // If kept, this would block the site from rendering + removeCspHeaderForPrerenderedRoutes(nuxt) + if (nuxt.options.security.requestSizeLimiter) { addServerHandler({ handler: normalize( @@ -125,7 +130,6 @@ export default defineNuxtModule({ ) }) } - if (nuxt.options.security.nonce) { addServerHandler({ handler: normalize( @@ -209,6 +213,17 @@ const setSecurityRouteRules = (nuxt: Nuxt, securityOptions: ModuleOptions) => { } } +const removeCspHeaderForPrerenderedRoutes = (nuxt: Nuxt) => { + const nitroRouteRules = nuxt.options.nitro.routeRules + for (const route in nitroRouteRules) { + const routeRules = nitroRouteRules[route] + if (routeRules.prerender) { + routeRules.headers = routeRules.headers || {} + routeRules.headers['Content-Security-Policy'] = '' + } + } +} + const registerSecurityNitroPlugins = ( nuxt: Nuxt, securityOptions: ModuleOptions diff --git a/src/runtime/nitro/plugins/99-cspNonce.ts b/src/runtime/nitro/plugins/99-cspNonce.ts index 47753a92..431b217d 100644 --- a/src/runtime/nitro/plugins/99-cspNonce.ts +++ b/src/runtime/nitro/plugins/99-cspNonce.ts @@ -24,8 +24,13 @@ const tagNotPrecededByQuotes = (tag: string) => new RegExp(`(? function (nitro) { nitro.hooks.hook('render:html', (html: NuxtRenderHTMLContext, { event }: { event: H3Event }) => { - console.log('pre', isPrerendering(event)) if (isPrerendering(event)) { + // In SSG mode, do not inject nonces in html + // However first make sure we erase nonce placeholders from CSP meta + html.head = html.head.map((meta) => { + if (!meta.startsWith(' function (nitro) { } /** - * Only enable behavior if Content Security pPolicy is enabled, - * initial page is prerendered and generated file type is HTML. + * Detect if page is being pre-rendered * @param event H3Event - * @param options ModuleOptions * @returns boolean */ function isPrerendering(event: H3Event): boolean { diff --git a/test/nonce.test.ts b/test/nonce.test.ts index 4361684c..45a85a29 100644 --- a/test/nonce.test.ts +++ b/test/nonce.test.ts @@ -87,12 +87,11 @@ describe('[nuxt-security] Nonce', async () => { const meta = body.match(//) const content = meta?.[1] const cspNonces = content?.match(/'nonce-(.*?)'/) - + expect(res).toBeDefined() expect(res).toBeTruthy() expect(content).toBeDefined() expect(injectedNonces).toBe(null) expect(cspNonces).toBe(null) - expect(content).toBe("base-uri 'self'; font-src 'self' https: data:; form-action 'self'; frame-ancestors 'self'; img-src 'self' data:; object-src 'none'; script-src-attr 'self' 'strict-dynamic'; style-src 'self' ; upgrade-insecure-requests; script-src 'self' 'strict-dynamic'") }) })