+
+
+
\ No newline at end of file
diff --git a/playground/pages/island.vue b/playground/pages/island.vue
new file mode 100644
index 00000000..9f18b75e
--- /dev/null
+++ b/playground/pages/island.vue
@@ -0,0 +1,6 @@
+
+
+ Island Page
+
+
+
\ No newline at end of file
diff --git a/src/runtime/nitro/plugins/40-cspSsrNonce.ts b/src/runtime/nitro/plugins/40-cspSsrNonce.ts
index e5b636e9..e796de65 100644
--- a/src/runtime/nitro/plugins/40-cspSsrNonce.ts
+++ b/src/runtime/nitro/plugins/40-cspSsrNonce.ts
@@ -1,5 +1,5 @@
import { defineNitroPlugin } from '#imports'
-import crypto from 'node:crypto'
+import { randomBytes } from 'node:crypto'
import { resolveSecurityRules } from '../context'
const LINK_RE = /]*?>)/gi
@@ -17,18 +17,32 @@ export default defineNitroPlugin((nitroApp) => {
return
}
+ // Genearate a 16-byte random nonce for each request.
nitroApp.hooks.hook('request', (event) => {
+ if (event.context.security?.nonce) {
+ // When rendering server-only (NuxtIsland) components, each component will trigger a request event.
+ // The request context is shared between the event that renders the actual page and the island request events.
+ // Make sure to only generate the nonce once.
+ return
+ }
+
const rules = resolveSecurityRules(event)
if (rules.enabled && rules.nonce && !import.meta.prerender) {
- const nonce = crypto.randomBytes(16).toString('base64')
+ const nonce = randomBytes(16).toString('base64')
event.context.security!.nonce = nonce
}
})
+ // Set the nonce attribute on all script, style, and link tags.
nitroApp.hooks.hook('render:html', (html, { event }) => {
// Exit if no CSP defined
const rules = resolveSecurityRules(event)
- if (!rules.enabled || !rules.headers || !rules.headers.contentSecurityPolicy || !rules.nonce) {
+ if (
+ !rules.enabled ||
+ !rules.headers ||
+ !rules.headers.contentSecurityPolicy ||
+ !rules.nonce
+ ) {
return
}
@@ -37,17 +51,17 @@ export default defineNitroPlugin((nitroApp) => {
type Section = 'body' | 'bodyAppend' | 'bodyPrepend' | 'head'
const sections = ['body', 'bodyAppend', 'bodyPrepend', 'head'] as Section[]
for (const section of sections) {
- html[section] = html[section].map(element => {
+ html[section] = html[section].map((element) => {
// Add nonce to all link tags
- element = element.replace(LINK_RE, (match, rest)=>{
+ element = element.replace(LINK_RE, (match, rest) => {
return `{
+ element = element.replace(SCRIPT_RE, (match, rest) => {
return `
\ No newline at end of file
diff --git a/test/fixtures/ssrNonce/pages/server-component.vue b/test/fixtures/ssrNonce/pages/server-component.vue
new file mode 100644
index 00000000..ef3098ad
--- /dev/null
+++ b/test/fixtures/ssrNonce/pages/server-component.vue
@@ -0,0 +1,5 @@
+
+