Skip to content

Commit

Permalink
Merge pull request #245 from vejja/fix/nonce-ssg
Browse files Browse the repository at this point in the history
Fix/nonce-ssg
  • Loading branch information
Baroshem authored Oct 25, 2023
2 parents 909221b + 9406b97 commit 92bddbe
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 1 deletion.
17 changes: 16 additions & 1 deletion src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ export default defineNuxtModule<ModuleOptions>({

setSecurityRouteRules(nuxt, securityOptions)

// Remove Content-Security-Policy header in pre-rendered routes
// When pre-rendered, the CSP is provided via html <meta> instead
// If kept, this would block the site from rendering
removeCspHeaderForPrerenderedRoutes(nuxt)

if (nuxt.options.security.requestSizeLimiter) {
addServerHandler({
handler: normalize(
Expand Down Expand Up @@ -113,7 +118,6 @@ export default defineNuxtModule<ModuleOptions>({
)
})
}

if (nuxt.options.security.nonce) {
addServerHandler({
handler: normalize(
Expand Down Expand Up @@ -197,6 +201,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
Expand Down
25 changes: 25 additions & 0 deletions src/runtime/nitro/plugins/99-cspNonce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ const tagNotPrecededByQuotes = (tag: string) => new RegExp(`(?<!['|"])<${tag}`,

export default <NitroAppPlugin> function (nitro) {
nitro.hooks.hook('render:html', (html: NuxtRenderHTMLContext, { event }: { event: H3Event }) => {
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('<meta http-equiv="Content-Security-Policy"')) { return meta }
return meta.replaceAll("'nonce-{{nonce}}'", '')
})
return
}
const nonce = parseNonce(`${event.node.res.getHeader('Content-Security-Policy')}`)

if (!nonce) { return }
Expand Down Expand Up @@ -54,4 +63,20 @@ export default <NitroAppPlugin> function (nitro) {
}
return null
}

/**
* Detect if page is being pre-rendered
* @param event H3Event
* @returns boolean
*/
function isPrerendering(event: H3Event): boolean {
const nitroPrerenderHeader = 'x-nitro-prerender'

// Page is not prerendered
if (!event.node.req.headers[nitroPrerenderHeader]) {
return false
}

return true
}
}
3 changes: 3 additions & 0 deletions test/fixtures/nonce/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ export default defineNuxtConfig({
security: {
nonce: false
}
},
'/prerendered': {
prerender: true
}
},
security: {
Expand Down
5 changes: 5 additions & 0 deletions test/fixtures/nonce/pages/prerendered.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<div>
prerendered page
</div>
</template>
16 changes: 16 additions & 0 deletions test/nonce.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,20 @@ describe('[nuxt-security] Nonce', async () => {
expect(nonce).toBeDefined()
expect(elementsWithNonce).toBe(expectedNonceElements + 1) // one extra for the style tag
})

it('removes the nonces in pre-render mode', async() => {
const res = await fetch('/prerendered')

const body = await res.text()
const injectedNonces = body.match(/ nonce="(.*?)"/)
const meta = body.match(/<meta http-equiv="Content-Security-Policy" content="(.*?)"(.*?)>/)
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)
})
})

0 comments on commit 92bddbe

Please sign in to comment.