From ff0ecfe7405c1fa405f9feaa291fa46b2697ccdc Mon Sep 17 00:00:00 2001 From: Jonas Thelemann Date: Wed, 12 Jun 2024 11:54:38 +0200 Subject: [PATCH 01/24] feat(csp): support style nonce in development --- .../1.documentation/2.headers/1.csp.md | 31 ------------------- src/runtime/nitro/plugins/40-cspSsrNonce.ts | 7 +++++ 2 files changed, 7 insertions(+), 31 deletions(-) diff --git a/docs/content/1.documentation/2.headers/1.csp.md b/docs/content/1.documentation/2.headers/1.csp.md index 8a5a90cb..a16d2380 100644 --- a/docs/content/1.documentation/2.headers/1.csp.md +++ b/docs/content/1.documentation/2.headers/1.csp.md @@ -217,15 +217,6 @@ export default defineNuxtConfig({ - `"'nonce-{{nonce}}'"` placeholder: Include this value in any individual policy that you want to be governed by nonce. -::alert{type="warning"} -Our default recommendation is to avoid using the `"'nonce-{{nonce}}'"` placeholder on `style-src` policy. -
-⚠ This is because Nuxt's mechanism for Client-Side hydration of styles could be blocked by CSP in that case. -
-For further discussion and alternatives, please refer to our [Advanced Section on Strict CSP](/documentation/advanced/strict-csp). -:: - - _Note: Nonce only works for SSR. The `nonce` option and the `"'nonce-{{nonce}}'"` placeholders are ignored when you build your app for SSG via `nuxi generate`._ @@ -306,28 +297,6 @@ Please see below our section on [Integrity Hashes For SSG](#integrity-hashes-for _Note: Hashes only work for SSG. The `ssg` options are ignored when you build your app for SSR via `nuxi build`._ - -## Hot reload during development - -If you have enabled `nonce-{{nonce}}` on `style-src`, you will need to disable it in order to allow hot reloading during development. - -```ts -export default defineNuxtConfig({ - security: { - nonce: true, - headers: { - contentSecurityPolicy: { - 'style-src': process.env.NODE_ENV === 'development' ? - ["'self'", "'unsafe-inline'"] : - ["'self'", "'unsafe-inline'", "nonce-{{nonce}}"] - } - } - } -}) -``` - -Note that this is not necessary if you use our default configuration settings. - ## Per-route configuration All Content Security Policy options can be defined on a per-route level. diff --git a/src/runtime/nitro/plugins/40-cspSsrNonce.ts b/src/runtime/nitro/plugins/40-cspSsrNonce.ts index ba97c305..e5b636e9 100644 --- a/src/runtime/nitro/plugins/40-cspSsrNonce.ts +++ b/src/runtime/nitro/plugins/40-cspSsrNonce.ts @@ -53,5 +53,12 @@ export default defineNitroPlugin((nitroApp) => { return element }) } + + // Add meta header for Vite in development + if (import.meta.dev) { + html.head.push( + ``, + ) + } }) }) From fad91ee57add9dc2511c2e5d396435e4379840cb Mon Sep 17 00:00:00 2001 From: vejja Date: Tue, 2 Jul 2024 00:35:59 +0200 Subject: [PATCH 02/24] Update from useScript to Nuxt Scripts --- .../1.documentation/5.advanced/3.strict-csp.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/content/1.documentation/5.advanced/3.strict-csp.md b/docs/content/1.documentation/5.advanced/3.strict-csp.md index 86f735ac..2693544d 100644 --- a/docs/content/1.documentation/5.advanced/3.strict-csp.md +++ b/docs/content/1.documentation/5.advanced/3.strict-csp.md @@ -312,7 +312,7 @@ export defaultNuxtConfig({ ### The `useScript` composable -Starting from Nuxt 3.11, it is possible to insert any external script in one single line with the new `useScript` composable. +The Nuxt Scripts module allows you to insert any external script in one single line with its `useScript` composable. ```ts useScript('https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js') @@ -324,24 +324,24 @@ The `useScript` method has several key features: - It does not insert inline event handlers, therefore CSP will never block the script from executing after load - It is designed to load and execute asynchronously, which means you don't have to write code to check whether the script has finished loading before using it -For all of these reasons, we strongly recommend `useScript` as the best way to load your external scripts in a CSP-compatible way. +In addition, Nuxt Scripts provide easy integration of `useScript` into any Nuxt application: +- A number of standard scripts are already pre-packaged +- You can load your scripts globally in `nuxt.config.ts` +- `useScript` is auto-imported -The `unjs/unhead` repo has a [detailed section here](https://unhead.unjs.io/usage/composables/use-script) on how to use `useScript`. +For all of these reasons, we strongly recommend using the Nuxt Scripts module as the best way to load your external scripts in a CSP-compatible way. -Check out their examples and find out how easy it is to include Google Analytics in your application: +Check out their examples on [@nuxt/scripts](https://scripts.nuxt.com) and find out how easy it is to include Google Analytics in your application: ```ts -import { useScript } from 'unhead' - -const { gtag } = useScript({ - src: 'https://www.google-analytics.com/analytics.js', -}, { +const { gtag } = useScript('https://www.google-analytics.com/analytics.js', { use: () => ({ gtag: window.gtag }) }) // Now use any feature of Google's gtag() function as you wish // Instead of writing complex code to find and check window.gtag ``` +If you don't want to install the Nuxt Scripts module, you can still use the uderlying native `useScript` method. You will need to `import { useScript } from '@unhead/vue'` in order to use it. ### The `useHead` composable From 338be115d077f1d95e372756a0378ed4a8976fb8 Mon Sep 17 00:00:00 2001 From: Baroshem Date: Tue, 2 Jul 2024 11:56:16 +0200 Subject: [PATCH 03/24] feat-#487: local dev with nuxt devtools --- .../1.documentation/1.getting-started/1.setup.md | 15 --------------- playground/nuxt.config.ts | 1 - src/defaultConfig.ts | 2 +- 3 files changed, 1 insertion(+), 17 deletions(-) diff --git a/docs/content/1.documentation/1.getting-started/1.setup.md b/docs/content/1.documentation/1.getting-started/1.setup.md index f35961b5..1189753c 100644 --- a/docs/content/1.documentation/1.getting-started/1.setup.md +++ b/docs/content/1.documentation/1.getting-started/1.setup.md @@ -24,18 +24,3 @@ security: { ``` You can find more about configuring `nuxt-security` [here](/documentation/getting-started/configuration). - -## Using with Nuxt DevTools - -In order to make this module work with Nuxt DevTools add following configuration to your projects: - -```js{}[nuxt.config.ts] -export default defineNuxtConfig({ - modules: ['nuxt-security', '@nuxt/devtools'], - security: { - headers: { - crossOriginEmbedderPolicy: process.env.NODE_ENV === 'development' ? 'unsafe-none' : 'require-corp', - }, - }, -}); -``` diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts index b76436a2..ecd18c12 100644 --- a/playground/nuxt.config.ts +++ b/playground/nuxt.config.ts @@ -48,7 +48,6 @@ export default defineNuxtConfig({ // Global configuration security: { headers: { - crossOriginEmbedderPolicy: false, xXSSProtection: '0' }, rateLimiter: { diff --git a/src/defaultConfig.ts b/src/defaultConfig.ts index 5b286d43..b04a5ad0 100644 --- a/src/defaultConfig.ts +++ b/src/defaultConfig.ts @@ -9,7 +9,7 @@ export const defaultSecurityConfig = (serverlUrl: string, strict: boolean) => { headers: { crossOriginResourcePolicy: 'same-origin', crossOriginOpenerPolicy: 'same-origin', - crossOriginEmbedderPolicy: 'credentialless', + crossOriginEmbedderPolicy: process.env.NODE_ENV === 'development' ? 'unsafe-none' : 'credentialless', contentSecurityPolicy: { 'base-uri': ["'none'"], 'font-src': ["'self'", 'https:', 'data:'], From 4528880f358dd4ef529d32ebd8b7b42e9a2af655 Mon Sep 17 00:00:00 2001 From: ShanaAE Date: Tue, 23 Jul 2024 00:52:23 +0800 Subject: [PATCH 04/24] fix: ensure RegExp[] origin can be passed to appSecurityOptions --- src/module.ts | 29 +++++++++------ src/runtime/nitro/plugins/00-routeRules.ts | 16 +++++++-- src/utils/originRegExpSerde.ts | 41 ++++++++++++++++++++++ 3 files changed, 72 insertions(+), 14 deletions(-) create mode 100644 src/utils/originRegExpSerde.ts diff --git a/src/module.ts b/src/module.ts index 6c9cbde6..2e399509 100644 --- a/src/module.ts +++ b/src/module.ts @@ -11,6 +11,7 @@ import { defaultSecurityConfig } from './defaultConfig' import type { Nuxt } from '@nuxt/schema' import type { Nitro } from 'nitropack' import type { ModuleOptions } from './types/module' +import { modifyCorsHandlerOriginRegExpToJSON } from './utils/originRegExpSerde' export * from './types/module' export * from './types/headers' @@ -23,7 +24,7 @@ export default defineNuxtModule({ }, async setup (options, nuxt) { const resolver = createResolver(import.meta.url) - + nuxt.options.build.transpile.push(resolver.resolve('./runtime')) // First merge module options with default options @@ -55,6 +56,9 @@ export default defineNuxtModule({ // At this point we have all security options merged into runtimeConfig const securityOptions = nuxt.options.runtimeConfig.security + // Avoid JSON.stringify regexp to '{}' + modifyCorsHandlerOriginRegExpToJSON(securityOptions.corsHandler) + // Disable module when `enabled` is set to `false` if (!securityOptions.enabled) { return } @@ -75,6 +79,9 @@ export default defineNuxtModule({ // Then insert route specific security headers for (const route in nuxt.options.nitro.routeRules) { const rule = nuxt.options.nitro.routeRules[route] + if (rule.security && rule.security.corsHandler) { + modifyCorsHandlerOriginRegExpToJSON(rule.security.corsHandler) + } if (rule.security && rule.security.headers) { const { security : { headers } } = rule const routeSecurityHeaders = getHeadersApplicableToAllResources(headers) @@ -84,7 +91,7 @@ export default defineNuxtModule({ ) } } - + // Register nitro plugin to manage security rules at the level of each route addServerPlugin(resolver.resolve('./runtime/nitro/plugins/00-routeRules')) @@ -135,12 +142,12 @@ export default defineNuxtModule({ addServerHandler({ handler: resolver.resolve('./runtime/server/middleware/rateLimiter') }) - + // Register XSS validator middleware addServerHandler({ handler: resolver.resolve('./runtime/server/middleware/xssValidator') }) - + // Register basicAuth middleware that is disabled by default const basicAuthConfig = nuxt.options.runtimeConfig.private.basicAuth if (basicAuthConfig && (basicAuthConfig.enabled || (basicAuthConfig as any)?.value?.enabled)) { @@ -174,12 +181,12 @@ export default defineNuxtModule({ }) // Register init hook to add pre-rendered headers to responses - nuxt.hook('nitro:init', nitro => { + nuxt.hook('nitro:init', nitro => { nitro.hooks.hook('prerender:done', async() => { // Add the prenredered headers to the Nitro server assets - nitro.options.serverAssets.push({ - baseName: 'nuxt-security', - dir: createResolver(nuxt.options.buildDir).resolve('./nuxt-security') + nitro.options.serverAssets.push({ + baseName: 'nuxt-security', + dir: createResolver(nuxt.options.buildDir).resolve('./nuxt-security') }) // In some Nitro presets (e.g. Vercel), the header rules are generated for the static server @@ -203,7 +210,7 @@ export default defineNuxtModule({ }) /** - * + * * Register storage driver for the rate limiter */ function registerRateLimiterStorage(nuxt: Nuxt, securityOptions: ModuleOptions) { @@ -229,8 +236,8 @@ function registerRateLimiterStorage(nuxt: Nuxt, securityOptions: ModuleOptions) * Make sure our nitro plugins will be applied last, * After all other third-party modules that might have loaded their own nitro plugins */ -function reorderNitroPlugins(nuxt: Nuxt) { - nuxt.hook('nitro:init', nitro => { +function reorderNitroPlugins(nuxt: Nuxt) { + nuxt.hook('nitro:init', nitro => { const resolver = createResolver(import.meta.url) const securityPluginsPrefix = resolver.resolve('./runtime/nitro/plugins') diff --git a/src/runtime/nitro/plugins/00-routeRules.ts b/src/runtime/nitro/plugins/00-routeRules.ts index 007a9781..805f59b1 100644 --- a/src/runtime/nitro/plugins/00-routeRules.ts +++ b/src/runtime/nitro/plugins/00-routeRules.ts @@ -2,6 +2,7 @@ import { defineNitroPlugin, useRuntimeConfig } from "#imports" import { getAppSecurityOptions } from '../context' import { defuReplaceArray } from '../../../utils/merge' import { standardToSecurity, backwardsCompatibleSecurity } from '../../../utils/headers' +import { getRegExpOriginRestoredCorsHandler } from '../../../utils/originRegExpSerde'; /** * This plugin merges all security options into the global security context @@ -24,9 +25,15 @@ export default defineNitroPlugin(async(nitroApp) => { const securityOptions = runtimeConfig.security const { headers } = securityOptions + // Restore origin regexp + const corsHandlerOption = getRegExpOriginRestoredCorsHandler(securityOptions.corsHandler) + const securityHeaders = backwardsCompatibleSecurity(headers) appSecurityOptions['/**'] = defuReplaceArray( - { headers: securityHeaders }, + { + headers: securityHeaders, + corsHandler: corsHandlerOption, + }, securityOptions, appSecurityOptions['/**'] ) @@ -40,8 +47,12 @@ export default defineNitroPlugin(async(nitroApp) => { if (security) { const { headers } = security const securityHeaders = backwardsCompatibleSecurity(headers) + const corsHandlerOption = getRegExpOriginRestoredCorsHandler(security.corsHandler) appSecurityOptions[route] = defuReplaceArray( - { headers: securityHeaders }, + { + headers: securityHeaders, + corsHandler: corsHandlerOption + }, security, appSecurityOptions[route], ) @@ -63,4 +74,3 @@ export default defineNitroPlugin(async(nitroApp) => { await nitroApp.hooks.callHook('nuxt-security:ready') }) - diff --git a/src/utils/originRegExpSerde.ts b/src/utils/originRegExpSerde.ts new file mode 100644 index 00000000..216b06b0 --- /dev/null +++ b/src/utils/originRegExpSerde.ts @@ -0,0 +1,41 @@ +import type { CorsOptions } from '../module'; + +const REG_SERIALIZE_KEY = '_nuxt_security_origin_serialized_regexp'; + +export const modifyRegExpToJSON = (reg: RegExp) => { + (reg as any).toJSON = () => ({ + [REG_SERIALIZE_KEY]: reg.toString(), + }); +}; + +export const getDeserializedRegExp = (regObj: Record) => { + const regStr = regObj?.[REG_SERIALIZE_KEY]; + if (typeof regStr !== 'string') return null; + const fragments = regStr.match(/\/(.*?)\/([a-z]*)?$/i); + if (!fragments?.length) return null; + return new RegExp(fragments?.[1], fragments?.[2] || ''); +}; + +export const modifyCorsHandlerOriginRegExpToJSON = (corsHandler?: CorsOptions | false) => { + if (typeof corsHandler === 'object' && Array.isArray(corsHandler.origin)) { + corsHandler.origin.forEach((o) => { + o instanceof RegExp && modifyRegExpToJSON(o); + }); + } +}; + +export const getRegExpOriginRestoredCorsHandler = (corsHandler?: CorsOptions | false) => { + let originOption = typeof corsHandler === 'object' ? corsHandler.origin : undefined; + if (typeof corsHandler !== 'object') return corsHandler; + if (Array.isArray(corsHandler.origin)) { + originOption = corsHandler.origin.map((o) => { + const origin = getDeserializedRegExp(o as any); + return origin ?? o; + }); + } + const result: CorsOptions = { + ...corsHandler, + origin: originOption, + }; + return result; +}; From 23af05a3352a57169adaa14310ab77ed395bdd17 Mon Sep 17 00:00:00 2001 From: Pascal Sthamer <10992664+P4sca1@users.noreply.github.com> Date: Wed, 31 Jul 2024 13:02:43 +0200 Subject: [PATCH 05/24] test: use nullish coalescing operator If the CSP header is malformed or does not exist, the value will be undefined. --- test/ssrNonce.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/ssrNonce.test.ts b/test/ssrNonce.test.ts index b98b3e9e..a9d99412 100644 --- a/test/ssrNonce.test.ts +++ b/test/ssrNonce.test.ts @@ -13,7 +13,7 @@ describe('[nuxt-security] Nonce', async () => { const res = await fetch('/') const cspHeaderValue = res.headers.get('content-security-policy') - const nonce = cspHeaderValue?.match(/'nonce-(.*?)'/)![1] + const nonce = cspHeaderValue?.match(/'nonce-(.*?)'/)?.[1] const text = await res.text() const nonceMatch = `nonce="${nonce}"`.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') @@ -44,7 +44,7 @@ describe('[nuxt-security] Nonce', async () => { const res = await fetch('/use-head') const cspHeaderValue = res.headers.get('content-security-policy') - const nonce = cspHeaderValue!.match(/'nonce-(.*?)'/)![1] + const nonce = cspHeaderValue!.match(/'nonce-(.*?)'/)?.[1] const text = await res.text() const nonceMatch = `nonce="${nonce}"`.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') @@ -70,7 +70,7 @@ describe('[nuxt-security] Nonce', async () => { const res = await fetch('/with-styling') const cspHeaderValue = res.headers.get('content-security-policy') - const nonce = cspHeaderValue?.match(/'nonce-(.*?)'/)![1] + const nonce = cspHeaderValue?.match(/'nonce-(.*?)'/)?.[1] const text = await res.text() const nonceMatch = `nonce="${nonce}"`.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') From eb097d0838c57447f4bdc3ca4b3abdbd48b77ea9 Mon Sep 17 00:00:00 2001 From: Pascal Sthamer <10992664+P4sca1@users.noreply.github.com> Date: Wed, 31 Jul 2024 13:10:14 +0200 Subject: [PATCH 06/24] test: add test cases for server-only components --- .../ssrNonce/components/ServerComponent.server.vue | 10 ++++++++++ test/fixtures/ssrNonce/pages/server-component.vue | 5 +++++ test/ssrNonce.test.ts | 14 ++++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 test/fixtures/ssrNonce/components/ServerComponent.server.vue create mode 100644 test/fixtures/ssrNonce/pages/server-component.vue diff --git a/test/fixtures/ssrNonce/components/ServerComponent.server.vue b/test/fixtures/ssrNonce/components/ServerComponent.server.vue new file mode 100644 index 00000000..ad7324ed --- /dev/null +++ b/test/fixtures/ssrNonce/components/ServerComponent.server.vue @@ -0,0 +1,10 @@ + + + \ 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 @@ + diff --git a/test/ssrNonce.test.ts b/test/ssrNonce.test.ts index a9d99412..1c8d8cd3 100644 --- a/test/ssrNonce.test.ts +++ b/test/ssrNonce.test.ts @@ -97,4 +97,18 @@ describe('[nuxt-security] Nonce', async () => { expect(injectedNonces).toBe(null) expect(cspNonces).toBe(null) }) + + it('works with server-only components', async () => { + const res = await fetch('/server-component') + + const cspHeaderValue = res.headers.get('content-security-policy') + const nonce = cspHeaderValue?.match(/'nonce-(.*?)'/)?.[1] + + const text = await res.text() + + expect(res).toBeDefined() + expect(res).toBeTruthy() + expect(nonce).toBeDefined() + expect(text).toMatch(`${nonce}`) + }) }) From c38a710fbdf6a091e8eec2eb6c5be89208f7adae Mon Sep 17 00:00:00 2001 From: Pascal Sthamer <10992664+P4sca1@users.noreply.github.com> Date: Wed, 31 Jul 2024 13:16:31 +0200 Subject: [PATCH 07/24] fix: log warning when removing static nonce from CSP header --- src/runtime/nitro/plugins/50-updateCsp.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/runtime/nitro/plugins/50-updateCsp.ts b/src/runtime/nitro/plugins/50-updateCsp.ts index 17cc7aa1..c16f793c 100644 --- a/src/runtime/nitro/plugins/50-updateCsp.ts +++ b/src/runtime/nitro/plugins/50-updateCsp.ts @@ -1,6 +1,7 @@ import { defineNitroPlugin } from '#imports' import { resolveSecurityRules } from '../context' import type { ContentSecurityPolicyValue } from '../../../types/headers' +import { useLogger } from '@nuxt/kit' /** * This plugin updates the CSP directives with the nonce and hashes generated by the server. @@ -23,6 +24,8 @@ export default defineNitroPlugin((nitroApp) => { }) function updateCspVariables(csp: ContentSecurityPolicyValue, nonce?: string, scriptHashes?: Set, styleHashes?: Set) { + const logger = useLogger('nuxt-security') + const generatedCsp = Object.fromEntries(Object.entries(csp).map(([directive, value]) => { // Return boolean values unchanged if (typeof value === 'boolean') { @@ -31,7 +34,13 @@ function updateCspVariables(csp: ContentSecurityPolicyValue, nonce?: string, scr // Make sure nonce placeholders are eliminated const sources = (typeof value === 'string') ? value.split(' ').map(token => token.trim()).filter(token => token) : value const modifiedSources = sources - .filter(source => !source.startsWith("'nonce-") || source === "'nonce-{{nonce}}'") + .filter(source => { + if (source.startsWith("'nonce-") && source !== "'nonce-{{nonce}}'") { + logger.warn('Removing static nonce from CSP header.') + return false + } + return true + }) .map(source => { if (source === "'nonce-{{nonce}}'") { return nonce ? `'nonce-${nonce}'` : '' From 2b0cf0f84ea74b5496dc10b9d8f710d4e1d1408e Mon Sep 17 00:00:00 2001 From: Pascal Sthamer <10992664+P4sca1@users.noreply.github.com> Date: Wed, 31 Jul 2024 13:31:21 +0200 Subject: [PATCH 08/24] fix: skip nonce generation and csp header update for NuxtIsland requests --- src/runtime/nitro/plugins/40-cspSsrNonce.ts | 29 ++++++++++++++++----- src/runtime/nitro/plugins/50-updateCsp.ts | 6 +++++ src/utils/island.ts | 5 ++++ 3 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 src/utils/island.ts diff --git a/src/runtime/nitro/plugins/40-cspSsrNonce.ts b/src/runtime/nitro/plugins/40-cspSsrNonce.ts index ba97c305..2bb1a7f2 100644 --- a/src/runtime/nitro/plugins/40-cspSsrNonce.ts +++ b/src/runtime/nitro/plugins/40-cspSsrNonce.ts @@ -1,6 +1,7 @@ import { defineNitroPlugin } from '#imports' -import crypto from 'node:crypto' +import { randomBytes } from 'node:crypto' import { resolveSecurityRules } from '../context' +import { isIslandRequst } from '../../../utils/island' const LINK_RE = /]*?>)/gi const SCRIPT_RE = /]*?>)/gi @@ -17,18 +18,32 @@ export default defineNitroPlugin((nitroApp) => { return } + // Genearate a 16-byte random nonce for each request. nitroApp.hooks.hook('request', (event) => { + if (isIslandRequst(event)) { + // 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. + // We only generate the nonce once for the page event. + 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 +52,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 ` -``` +When using `` or ``, an inline script will be used for error handling during SSR. +This will lead to CSP issues if `unsafe-inline` is not allowed and the image fails to load. +Using nonces for inline event handlers is not supported, so currently there is no workaround. ::alert{type="info"} -ℹ Read more about it [here](https://github.com/Baroshem/nuxt-security/issues/218#issuecomment-1736940913). +ℹ Read more about it [here](https://github.com/nuxt/image/issues/1011#issuecomment-2242761992). :: ## Issue on Firefox when using IFrame From 0e3ab07df59f79bc3629222b0ddbd6cc91fed104 Mon Sep 17 00:00:00 2001 From: Pascal Sthamer <10992664+P4sca1@users.noreply.github.com> Date: Wed, 31 Jul 2024 13:58:34 +0200 Subject: [PATCH 10/24] chore: fix typo Signed-off-by: Pascal Sthamer <10992664+P4sca1@users.noreply.github.com> --- src/runtime/nitro/plugins/40-cspSsrNonce.ts | 4 ++-- src/utils/island.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/runtime/nitro/plugins/40-cspSsrNonce.ts b/src/runtime/nitro/plugins/40-cspSsrNonce.ts index 2bb1a7f2..6cf32f34 100644 --- a/src/runtime/nitro/plugins/40-cspSsrNonce.ts +++ b/src/runtime/nitro/plugins/40-cspSsrNonce.ts @@ -1,7 +1,7 @@ import { defineNitroPlugin } from '#imports' import { randomBytes } from 'node:crypto' import { resolveSecurityRules } from '../context' -import { isIslandRequst } from '../../../utils/island' +import { isIslandRequest } from '../../../utils/island' const LINK_RE = /]*?>)/gi const SCRIPT_RE = /]*?>)/gi @@ -20,7 +20,7 @@ export default defineNitroPlugin((nitroApp) => { // Genearate a 16-byte random nonce for each request. nitroApp.hooks.hook('request', (event) => { - if (isIslandRequst(event)) { + if (isIslandRequest(event)) { // 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. // We only generate the nonce once for the page event. diff --git a/src/utils/island.ts b/src/utils/island.ts index 76c5e164..9edda5aa 100644 --- a/src/utils/island.ts +++ b/src/utils/island.ts @@ -1,5 +1,5 @@ import type { H3Event } from 'h3' -export function isIslandRequst(event: H3Event) { +export function isIslandRequest(event: H3Event) { return event.path.startsWith('/__nuxt_island/') } \ No newline at end of file From b0b4a08af8f65e2075bc0f062bbe28d161b676ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Raffray?= Date: Fri, 2 Aug 2024 20:18:22 +0200 Subject: [PATCH 11/24] merge changes from #500 into #502 --- package.json | 4 ++-- playground/components/ServerComponent.server.vue | 10 ++++++++++ playground/pages/island.vue | 6 ++++++ src/runtime/nitro/plugins/60-recombineHtml.ts | 10 ++++++---- 4 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 playground/components/ServerComponent.server.vue create mode 100644 playground/pages/island.vue diff --git a/package.json b/package.json index a94f13c5..836cc7b4 100644 --- a/package.json +++ b/package.json @@ -67,8 +67,8 @@ "@types/node": "^18.18.1", "eslint": "^8.50.0", "nuxt": "^3.11.2", - "vitest": "^1.3.1", - "typescript": "^5.4.5" + "typescript": "^5.4.5", + "vitest": "^1.3.1" }, "stackblitz": { "installDependencies": false, diff --git a/playground/components/ServerComponent.server.vue b/playground/components/ServerComponent.server.vue new file mode 100644 index 00000000..ad7324ed --- /dev/null +++ b/playground/components/ServerComponent.server.vue @@ -0,0 +1,10 @@ + + + \ 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 @@ + \ No newline at end of file diff --git a/src/runtime/nitro/plugins/60-recombineHtml.ts b/src/runtime/nitro/plugins/60-recombineHtml.ts index a7f4ed01..6cc082a3 100644 --- a/src/runtime/nitro/plugins/60-recombineHtml.ts +++ b/src/runtime/nitro/plugins/60-recombineHtml.ts @@ -24,11 +24,13 @@ export default defineNitroPlugin((nitroApp) => { // Let's insert the CSP meta tag just after the first tag which should be the charset meta let insertIndex = 0 - const metaCharsetMatch = html.head[0].match(/^/mdi) - if (metaCharsetMatch && metaCharsetMatch.indices) { - insertIndex = metaCharsetMatch.indices[0][1] + if (html.head.length > 0) { + const metaCharsetMatch = html.head[0].match(/^/mdi) + if (metaCharsetMatch && metaCharsetMatch.indices) { + insertIndex = metaCharsetMatch.indices[0][1] + } + html.head[0] = html.head[0].slice(0, insertIndex) + `` + html.head[0].slice(insertIndex) } - html.head[0] = html.head[0].slice(0, insertIndex) + `` + html.head[0].slice(insertIndex) } }) }) \ No newline at end of file From 57ff90bc6ce8e3a3a5ac614a15d9d053805a109d Mon Sep 17 00:00:00 2001 From: Pascal Sthamer <10992664+P4sca1@users.noreply.github.com> Date: Sat, 3 Aug 2024 13:50:55 +0200 Subject: [PATCH 12/24] Replace isIslandRequest util with check if nonce already exist Signed-off-by: Pascal Sthamer <10992664+P4sca1@users.noreply.github.com> --- src/runtime/nitro/plugins/40-cspSsrNonce.ts | 5 ++--- src/utils/island.ts | 5 ----- 2 files changed, 2 insertions(+), 8 deletions(-) delete mode 100644 src/utils/island.ts diff --git a/src/runtime/nitro/plugins/40-cspSsrNonce.ts b/src/runtime/nitro/plugins/40-cspSsrNonce.ts index 6cf32f34..1f8f2321 100644 --- a/src/runtime/nitro/plugins/40-cspSsrNonce.ts +++ b/src/runtime/nitro/plugins/40-cspSsrNonce.ts @@ -1,7 +1,6 @@ import { defineNitroPlugin } from '#imports' import { randomBytes } from 'node:crypto' import { resolveSecurityRules } from '../context' -import { isIslandRequest } from '../../../utils/island' const LINK_RE = /]*?>)/gi const SCRIPT_RE = /]*?>)/gi @@ -20,10 +19,10 @@ export default defineNitroPlugin((nitroApp) => { // Genearate a 16-byte random nonce for each request. nitroApp.hooks.hook('request', (event) => { - if (isIslandRequest(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. - // We only generate the nonce once for the page event. + // Make sure to only generate the nonce once. return } diff --git a/src/utils/island.ts b/src/utils/island.ts deleted file mode 100644 index 9edda5aa..00000000 --- a/src/utils/island.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { H3Event } from 'h3' - -export function isIslandRequest(event: H3Event) { - return event.path.startsWith('/__nuxt_island/') -} \ No newline at end of file From 1a5ada98f74b8adf1a7de2d10a586495be0688d1 Mon Sep 17 00:00:00 2001 From: Pascal Sthamer <10992664+P4sca1@users.noreply.github.com> Date: Sat, 3 Aug 2024 14:07:55 +0200 Subject: [PATCH 13/24] fix: use console warn instead of useLogger Signed-off-by: Pascal Sthamer <10992664+P4sca1@users.noreply.github.com> --- src/runtime/nitro/plugins/50-updateCsp.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/runtime/nitro/plugins/50-updateCsp.ts b/src/runtime/nitro/plugins/50-updateCsp.ts index 8a77e36f..50bd3377 100644 --- a/src/runtime/nitro/plugins/50-updateCsp.ts +++ b/src/runtime/nitro/plugins/50-updateCsp.ts @@ -1,7 +1,6 @@ import { defineNitroPlugin } from '#imports' import { resolveSecurityRules } from '../context' import type { ContentSecurityPolicyValue } from '../../../types/headers' -import { useLogger } from '@nuxt/kit' /** * This plugin updates the CSP directives with the nonce and hashes generated by the server. @@ -30,8 +29,6 @@ export default defineNitroPlugin((nitroApp) => { }) function updateCspVariables(csp: ContentSecurityPolicyValue, nonce?: string, scriptHashes?: Set, styleHashes?: Set) { - const logger = useLogger('nuxt-security') - const generatedCsp = Object.fromEntries(Object.entries(csp).map(([directive, value]) => { // Return boolean values unchanged if (typeof value === 'boolean') { @@ -42,7 +39,7 @@ function updateCspVariables(csp: ContentSecurityPolicyValue, nonce?: string, scr const modifiedSources = sources .filter(source => { if (source.startsWith("'nonce-") && source !== "'nonce-{{nonce}}'") { - logger.warn('Removing static nonce from CSP header.') + console.warn('[nuxt-security] removing static nonce from CSP header') return false } return true From 4993963af2c6fa9c179ecd6b9eae55e3daa32024 Mon Sep 17 00:00:00 2001 From: Baroshem Date: Thu, 8 Aug 2024 13:42:12 +0200 Subject: [PATCH 14/24] feat: bump unplugin-remove to fix sitemaps --- package.json | 2 +- yarn.lock | 276 +++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 238 insertions(+), 40 deletions(-) diff --git a/package.json b/package.json index 836cc7b4..6a441bdd 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "defu": "^6.1.1", "nuxt-csurf": "^1.5.1", "pathe": "^1.0.0", - "unplugin-remove": "^1.0.2", + "unplugin-remove": "^1.0.3", "xss": "^1.0.14" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index eec2b29a..456ed876 100644 --- a/yarn.lock +++ b/yarn.lock @@ -28,12 +28,25 @@ "@babel/highlight" "^7.24.2" picocolors "^1.0.0" +"@babel/code-frame@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" + integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA== + dependencies: + "@babel/highlight" "^7.24.7" + picocolors "^1.0.0" + "@babel/compat-data@^7.23.5": version "7.24.4" resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz" integrity sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ== -"@babel/core@^7.23.0", "@babel/core@^7.23.3", "@babel/core@^7.23.7", "@babel/core@^7.24.4": +"@babel/compat-data@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.2.tgz#e41928bd33475305c586f6acbbb7e3ade7a6f7f5" + integrity sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ== + +"@babel/core@^7.23.0", "@babel/core@^7.23.3", "@babel/core@^7.23.7": version "7.24.4" resolved "https://registry.npmjs.org/@babel/core/-/core-7.24.4.tgz" integrity sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg== @@ -54,6 +67,27 @@ json5 "^2.2.3" semver "^6.3.1" +"@babel/core@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.25.2.tgz#ed8eec275118d7613e77a352894cd12ded8eba77" + integrity sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.25.0" + "@babel/helper-compilation-targets" "^7.25.2" + "@babel/helper-module-transforms" "^7.25.2" + "@babel/helpers" "^7.25.0" + "@babel/parser" "^7.25.0" + "@babel/template" "^7.25.0" + "@babel/traverse" "^7.25.2" + "@babel/types" "^7.25.2" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + "@babel/generator@^7.24.1", "@babel/generator@^7.24.4": version "7.24.4" resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.24.4.tgz" @@ -64,6 +98,16 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" +"@babel/generator@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.0.tgz#f858ddfa984350bc3d3b7f125073c9af6988f18e" + integrity sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw== + dependencies: + "@babel/types" "^7.25.0" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + "@babel/helper-annotate-as-pure@^7.22.5": version "7.22.5" resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz" @@ -82,6 +126,17 @@ lru-cache "^5.1.1" semver "^6.3.1" +"@babel/helper-compilation-targets@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz#e1d9410a90974a3a5a66e84ff55ef62e3c02d06c" + integrity sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw== + dependencies: + "@babel/compat-data" "^7.25.2" + "@babel/helper-validator-option" "^7.24.8" + browserslist "^4.23.1" + lru-cache "^5.1.1" + semver "^6.3.1" + "@babel/helper-create-class-features-plugin@^7.24.1", "@babel/helper-create-class-features-plugin@^7.24.4": version "7.24.4" resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.4.tgz" @@ -131,6 +186,14 @@ dependencies: "@babel/types" "^7.24.0" +"@babel/helper-module-imports@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz#f2f980392de5b84c3328fc71d38bd81bbb83042b" + integrity sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA== + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + "@babel/helper-module-imports@~7.22.15": version "7.22.15" resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz" @@ -149,6 +212,16 @@ "@babel/helper-split-export-declaration" "^7.22.6" "@babel/helper-validator-identifier" "^7.22.20" +"@babel/helper-module-transforms@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz#ee713c29768100f2776edf04d4eb23b8d27a66e6" + integrity sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ== + dependencies: + "@babel/helper-module-imports" "^7.24.7" + "@babel/helper-simple-access" "^7.24.7" + "@babel/helper-validator-identifier" "^7.24.7" + "@babel/traverse" "^7.25.2" + "@babel/helper-optimise-call-expression@^7.22.5": version "7.22.5" resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz" @@ -177,6 +250,14 @@ dependencies: "@babel/types" "^7.22.5" +"@babel/helper-simple-access@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz#bcade8da3aec8ed16b9c4953b74e506b51b5edb3" + integrity sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg== + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + "@babel/helper-skip-transparent-expression-wrappers@^7.22.5": version "7.22.5" resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz" @@ -196,16 +277,31 @@ resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz" integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ== +"@babel/helper-string-parser@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d" + integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ== + "@babel/helper-validator-identifier@^7.22.20": version "7.22.20" resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz" integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== +"@babel/helper-validator-identifier@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" + integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== + "@babel/helper-validator-option@^7.23.5": version "7.23.5" resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz" integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== +"@babel/helper-validator-option@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d" + integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q== + "@babel/helpers@^7.24.4": version "7.24.4" resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.4.tgz" @@ -215,6 +311,14 @@ "@babel/traverse" "^7.24.1" "@babel/types" "^7.24.0" +"@babel/helpers@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.0.tgz#e69beb7841cb93a6505531ede34f34e6a073650a" + integrity sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw== + dependencies: + "@babel/template" "^7.25.0" + "@babel/types" "^7.25.0" + "@babel/highlight@^7.24.2": version "7.24.2" resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz" @@ -225,11 +329,28 @@ js-tokens "^4.0.0" picocolors "^1.0.0" +"@babel/highlight@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d" + integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw== + dependencies: + "@babel/helper-validator-identifier" "^7.24.7" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + "@babel/parser@^7.22.7", "@babel/parser@^7.23.6", "@babel/parser@^7.23.9", "@babel/parser@^7.24.0", "@babel/parser@^7.24.1", "@babel/parser@^7.24.4": version "7.24.4" resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz" integrity sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg== +"@babel/parser@^7.25.0", "@babel/parser@^7.25.3": + version "7.25.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.3.tgz#91fb126768d944966263f0657ab222a642b82065" + integrity sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw== + dependencies: + "@babel/types" "^7.25.2" + "@babel/plugin-proposal-decorators@^7.23.0": version "7.24.1" resolved "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.24.1.tgz" @@ -298,6 +419,15 @@ "@babel/parser" "^7.24.0" "@babel/types" "^7.24.0" +"@babel/template@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.0.tgz#e733dc3134b4fede528c15bc95e89cb98c52592a" + integrity sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/parser" "^7.25.0" + "@babel/types" "^7.25.0" + "@babel/traverse@^7.23.9", "@babel/traverse@^7.24.1": version "7.24.1" resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz" @@ -314,6 +444,19 @@ debug "^4.3.1" globals "^11.1.0" +"@babel/traverse@^7.24.7", "@babel/traverse@^7.25.2", "@babel/traverse@^7.25.3": + version "7.25.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.3.tgz#f1b901951c83eda2f3e29450ce92743783373490" + integrity sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.25.0" + "@babel/parser" "^7.25.3" + "@babel/template" "^7.25.0" + "@babel/types" "^7.25.2" + debug "^4.3.1" + globals "^11.1.0" + "@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.6", "@babel/types@^7.23.9", "@babel/types@^7.24.0": version "7.24.0" resolved "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz" @@ -323,6 +466,15 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" +"@babel/types@^7.24.7", "@babel/types@^7.25.0", "@babel/types@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.2.tgz#55fb231f7dc958cd69ea141a4c2997e819646125" + integrity sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q== + dependencies: + "@babel/helper-string-parser" "^7.24.8" + "@babel/helper-validator-identifier" "^7.24.7" + to-fast-properties "^2.0.0" + "@cloudflare/kv-asset-handler@^0.3.1": version "0.3.1" resolved "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.1.tgz" @@ -686,6 +838,11 @@ resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== +"@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": version "0.3.25" resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz" @@ -2057,6 +2214,11 @@ acorn@8.11.3, acorn@^8.10.0, acorn@^8.11.2, acorn@^8.11.3, acorn@^8.6.0, acorn@^ resolved "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== +acorn@^8.12.1: + version "8.12.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== + agent-base@6: version "6.0.2" resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" @@ -2330,6 +2492,16 @@ browserslist@^4.0.0, browserslist@^4.22.2, browserslist@^4.23.0: node-releases "^2.0.14" update-browserslist-db "^1.0.13" +browserslist@^4.23.1: + version "4.23.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.3.tgz#debb029d3c93ebc97ffbc8d9cbb03403e227c800" + integrity sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA== + dependencies: + caniuse-lite "^1.0.30001646" + electron-to-chromium "^1.5.4" + node-releases "^2.0.18" + update-browserslist-db "^1.1.0" + buffer-crc32@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz" @@ -2433,6 +2605,11 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001599: resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001605.tgz" integrity sha512-nXwGlFWo34uliI9z3n6Qc0wZaf7zaZWA1CPZ169La5mV3I/gem7bst0vr5XQH5TJXZIMfDeZyOrZnSlVzKxxHQ== +caniuse-lite@^1.0.30001646: + version "1.0.30001651" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz#52de59529e8b02b1aedcaaf5c05d9e23c0c28138" + integrity sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg== + chai@^4.3.10: version "4.4.1" resolved "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz" @@ -3011,6 +3188,11 @@ electron-to-chromium@^1.4.668: resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.727.tgz" integrity sha512-brpv4KTeC4g0Fx2FeIKytLd4UGn1zBQq5Lauy7zEWT9oqkaj5mgsxblEZIAOf1HHLlXxzr6adGViiBy5Z39/CA== +electron-to-chromium@^1.5.4: + version "1.5.5" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.5.tgz#03bfdf422bdd2c05ee2657efedde21264a1a566b" + integrity sha512-QR7/A7ZkMS8tZuoftC/jfqNkZLQO779SSW3YuZHP4eXpj3EffGLFcB/Xu9AAZQzLccTiCV+EmUo3ha4mQ9wnlA== + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" @@ -3126,7 +3308,7 @@ esbuild@^0.20.1, esbuild@^0.20.2: "@esbuild/win32-ia32" "0.20.2" "@esbuild/win32-x64" "0.20.2" -escalade@^3.1.1: +escalade@^3.1.1, escalade@^3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz" integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== @@ -4456,6 +4638,13 @@ magic-string@^0.30.0, magic-string@^0.30.2, magic-string@^0.30.3, magic-string@^ dependencies: "@jridgewell/sourcemap-codec" "^1.4.15" +magic-string@^0.30.11: + version "0.30.11" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.11.tgz#301a6f93b3e8c2cb13ac1a7a673492c0dfd12954" + integrity sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + magicast@^0.3.3: version "0.3.3" resolved "https://registry.npmjs.org/magicast/-/magicast-0.3.3.tgz" @@ -4857,6 +5046,11 @@ node-releases@^2.0.14: resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz" integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== +node-releases@^2.0.18: + version "2.0.18" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== + nopt@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz" @@ -5366,6 +5560,11 @@ picocolors@^1.0.0: resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +picocolors@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" + integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" @@ -6259,16 +6458,7 @@ streamx@^2.15.0: optionalDependencies: bare-events "^2.2.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -6300,14 +6490,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -6697,17 +6880,18 @@ universalify@^2.0.0: resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz" integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== -unplugin-remove@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/unplugin-remove/-/unplugin-remove-1.0.2.tgz" - integrity sha512-mZrgNpd5WcMDYW2LI81aUPVr49Qn0ii1+DI3Tg8oYRPApyf35IqlwbdQYdE1p1wdWKSyzoSRUFZecJUMF6goIw== +unplugin-remove@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/unplugin-remove/-/unplugin-remove-1.0.3.tgz#daf24110c2a0f5f679bb72aed9418078b1008043" + integrity sha512-BZMt9v8Y/Z27cY7YQv+DpcW928znjP1cqplBXOirbANiFQtM2YCdiyNAJhHCvjppT0lScNn1aDrQnXqnRp32pQ== dependencies: - "@babel/core" "^7.24.4" - "@babel/generator" "^7.24.4" - "@babel/parser" "^7.24.4" - "@babel/traverse" "^7.24.1" + "@babel/core" "^7.25.2" + "@babel/generator" "^7.25.0" + "@babel/parser" "^7.25.3" + "@babel/traverse" "^7.25.3" "@rollup/pluginutils" "^5.1.0" - unplugin "^1.10.1" + magic-string "^0.30.11" + unplugin "^1.12.0" unplugin-vue-router@^0.7.0: version "0.7.0" @@ -6738,6 +6922,16 @@ unplugin@^1.10.0, unplugin@^1.10.1, unplugin@^1.3.1, unplugin@^1.5.0, unplugin@^ webpack-sources "^3.2.3" webpack-virtual-modules "^0.6.1" +unplugin@^1.12.0: + version "1.12.1" + resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-1.12.1.tgz#dc5834dc9337f47ddb7cf4cbbb9b8dac07e5bea4" + integrity sha512-aXEH9c5qi3uYZHo0niUtxDlT9ylG/luMW/dZslSCkbtC31wCyFkmM0kyoBBh+Grhn7CL+/kvKLfN61/EdxPxMQ== + dependencies: + acorn "^8.12.1" + chokidar "^3.6.0" + webpack-sources "^3.2.3" + webpack-virtual-modules "^0.6.2" + unstorage@^1.10.2: version "1.10.2" resolved "https://registry.npmjs.org/unstorage/-/unstorage-1.10.2.tgz" @@ -6796,6 +6990,14 @@ update-browserslist-db@^1.0.13: escalade "^3.1.1" picocolors "^1.0.0" +update-browserslist-db@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e" + integrity sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ== + dependencies: + escalade "^3.1.2" + picocolors "^1.0.1" + uqr@^0.1.2: version "0.1.2" resolved "https://registry.npmjs.org/uqr/-/uqr-0.1.2.tgz" @@ -7070,6 +7272,11 @@ webpack-virtual-modules@^0.6.1: resolved "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.1.tgz" integrity sha512-poXpCylU7ExuvZK8z+On3kX+S8o/2dQ/SVYueKA0D4WEMXROXgY8Ez50/bQEUmvoSMMrWcrJqCHuhAbsiwg7Dg== +webpack-virtual-modules@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz#057faa9065c8acf48f24cb57ac0e77739ab9a7e8" + integrity sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ== + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" @@ -7114,16 +7321,7 @@ wide-align@^1.1.2: dependencies: string-width "^1.0.2 || 2 || 3 || 4" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== From b133ed634ce8b89d4f72c1ce951b9791e0edf966 Mon Sep 17 00:00:00 2001 From: Baroshem Date: Fri, 9 Aug 2024 13:50:41 +0200 Subject: [PATCH 15/24] Revert "fix: ensure RegExp[] origin can be passed to appSecurityOptions" This reverts commit 4528880f358dd4ef529d32ebd8b7b42e9a2af655. --- src/module.ts | 29 ++++++--------- src/runtime/nitro/plugins/00-routeRules.ts | 16 ++------- src/utils/originRegExpSerde.ts | 41 ---------------------- 3 files changed, 14 insertions(+), 72 deletions(-) delete mode 100644 src/utils/originRegExpSerde.ts diff --git a/src/module.ts b/src/module.ts index 2e399509..6c9cbde6 100644 --- a/src/module.ts +++ b/src/module.ts @@ -11,7 +11,6 @@ import { defaultSecurityConfig } from './defaultConfig' import type { Nuxt } from '@nuxt/schema' import type { Nitro } from 'nitropack' import type { ModuleOptions } from './types/module' -import { modifyCorsHandlerOriginRegExpToJSON } from './utils/originRegExpSerde' export * from './types/module' export * from './types/headers' @@ -24,7 +23,7 @@ export default defineNuxtModule({ }, async setup (options, nuxt) { const resolver = createResolver(import.meta.url) - + nuxt.options.build.transpile.push(resolver.resolve('./runtime')) // First merge module options with default options @@ -56,9 +55,6 @@ export default defineNuxtModule({ // At this point we have all security options merged into runtimeConfig const securityOptions = nuxt.options.runtimeConfig.security - // Avoid JSON.stringify regexp to '{}' - modifyCorsHandlerOriginRegExpToJSON(securityOptions.corsHandler) - // Disable module when `enabled` is set to `false` if (!securityOptions.enabled) { return } @@ -79,9 +75,6 @@ export default defineNuxtModule({ // Then insert route specific security headers for (const route in nuxt.options.nitro.routeRules) { const rule = nuxt.options.nitro.routeRules[route] - if (rule.security && rule.security.corsHandler) { - modifyCorsHandlerOriginRegExpToJSON(rule.security.corsHandler) - } if (rule.security && rule.security.headers) { const { security : { headers } } = rule const routeSecurityHeaders = getHeadersApplicableToAllResources(headers) @@ -91,7 +84,7 @@ export default defineNuxtModule({ ) } } - + // Register nitro plugin to manage security rules at the level of each route addServerPlugin(resolver.resolve('./runtime/nitro/plugins/00-routeRules')) @@ -142,12 +135,12 @@ export default defineNuxtModule({ addServerHandler({ handler: resolver.resolve('./runtime/server/middleware/rateLimiter') }) - + // Register XSS validator middleware addServerHandler({ handler: resolver.resolve('./runtime/server/middleware/xssValidator') }) - + // Register basicAuth middleware that is disabled by default const basicAuthConfig = nuxt.options.runtimeConfig.private.basicAuth if (basicAuthConfig && (basicAuthConfig.enabled || (basicAuthConfig as any)?.value?.enabled)) { @@ -181,12 +174,12 @@ export default defineNuxtModule({ }) // Register init hook to add pre-rendered headers to responses - nuxt.hook('nitro:init', nitro => { + nuxt.hook('nitro:init', nitro => { nitro.hooks.hook('prerender:done', async() => { // Add the prenredered headers to the Nitro server assets - nitro.options.serverAssets.push({ - baseName: 'nuxt-security', - dir: createResolver(nuxt.options.buildDir).resolve('./nuxt-security') + nitro.options.serverAssets.push({ + baseName: 'nuxt-security', + dir: createResolver(nuxt.options.buildDir).resolve('./nuxt-security') }) // In some Nitro presets (e.g. Vercel), the header rules are generated for the static server @@ -210,7 +203,7 @@ export default defineNuxtModule({ }) /** - * + * * Register storage driver for the rate limiter */ function registerRateLimiterStorage(nuxt: Nuxt, securityOptions: ModuleOptions) { @@ -236,8 +229,8 @@ function registerRateLimiterStorage(nuxt: Nuxt, securityOptions: ModuleOptions) * Make sure our nitro plugins will be applied last, * After all other third-party modules that might have loaded their own nitro plugins */ -function reorderNitroPlugins(nuxt: Nuxt) { - nuxt.hook('nitro:init', nitro => { +function reorderNitroPlugins(nuxt: Nuxt) { + nuxt.hook('nitro:init', nitro => { const resolver = createResolver(import.meta.url) const securityPluginsPrefix = resolver.resolve('./runtime/nitro/plugins') diff --git a/src/runtime/nitro/plugins/00-routeRules.ts b/src/runtime/nitro/plugins/00-routeRules.ts index 805f59b1..007a9781 100644 --- a/src/runtime/nitro/plugins/00-routeRules.ts +++ b/src/runtime/nitro/plugins/00-routeRules.ts @@ -2,7 +2,6 @@ import { defineNitroPlugin, useRuntimeConfig } from "#imports" import { getAppSecurityOptions } from '../context' import { defuReplaceArray } from '../../../utils/merge' import { standardToSecurity, backwardsCompatibleSecurity } from '../../../utils/headers' -import { getRegExpOriginRestoredCorsHandler } from '../../../utils/originRegExpSerde'; /** * This plugin merges all security options into the global security context @@ -25,15 +24,9 @@ export default defineNitroPlugin(async(nitroApp) => { const securityOptions = runtimeConfig.security const { headers } = securityOptions - // Restore origin regexp - const corsHandlerOption = getRegExpOriginRestoredCorsHandler(securityOptions.corsHandler) - const securityHeaders = backwardsCompatibleSecurity(headers) appSecurityOptions['/**'] = defuReplaceArray( - { - headers: securityHeaders, - corsHandler: corsHandlerOption, - }, + { headers: securityHeaders }, securityOptions, appSecurityOptions['/**'] ) @@ -47,12 +40,8 @@ export default defineNitroPlugin(async(nitroApp) => { if (security) { const { headers } = security const securityHeaders = backwardsCompatibleSecurity(headers) - const corsHandlerOption = getRegExpOriginRestoredCorsHandler(security.corsHandler) appSecurityOptions[route] = defuReplaceArray( - { - headers: securityHeaders, - corsHandler: corsHandlerOption - }, + { headers: securityHeaders }, security, appSecurityOptions[route], ) @@ -74,3 +63,4 @@ export default defineNitroPlugin(async(nitroApp) => { await nitroApp.hooks.callHook('nuxt-security:ready') }) + diff --git a/src/utils/originRegExpSerde.ts b/src/utils/originRegExpSerde.ts deleted file mode 100644 index 216b06b0..00000000 --- a/src/utils/originRegExpSerde.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { CorsOptions } from '../module'; - -const REG_SERIALIZE_KEY = '_nuxt_security_origin_serialized_regexp'; - -export const modifyRegExpToJSON = (reg: RegExp) => { - (reg as any).toJSON = () => ({ - [REG_SERIALIZE_KEY]: reg.toString(), - }); -}; - -export const getDeserializedRegExp = (regObj: Record) => { - const regStr = regObj?.[REG_SERIALIZE_KEY]; - if (typeof regStr !== 'string') return null; - const fragments = regStr.match(/\/(.*?)\/([a-z]*)?$/i); - if (!fragments?.length) return null; - return new RegExp(fragments?.[1], fragments?.[2] || ''); -}; - -export const modifyCorsHandlerOriginRegExpToJSON = (corsHandler?: CorsOptions | false) => { - if (typeof corsHandler === 'object' && Array.isArray(corsHandler.origin)) { - corsHandler.origin.forEach((o) => { - o instanceof RegExp && modifyRegExpToJSON(o); - }); - } -}; - -export const getRegExpOriginRestoredCorsHandler = (corsHandler?: CorsOptions | false) => { - let originOption = typeof corsHandler === 'object' ? corsHandler.origin : undefined; - if (typeof corsHandler !== 'object') return corsHandler; - if (Array.isArray(corsHandler.origin)) { - originOption = corsHandler.origin.map((o) => { - const origin = getDeserializedRegExp(o as any); - return origin ?? o; - }); - } - const result: CorsOptions = { - ...corsHandler, - origin: originOption, - }; - return result; -}; From 6d1620155d09d45563c23560288d63ead9a2a8a6 Mon Sep 17 00:00:00 2001 From: Pascal Sthamer <10992664+P4sca1@users.noreply.github.com> Date: Fri, 9 Aug 2024 17:50:06 +0200 Subject: [PATCH 16/24] fix: limit cors options to serializable types, support RegExp Signed-off-by: Pascal Sthamer <10992664+P4sca1@users.noreply.github.com> --- .../3.middleware/4.cors-handler.md | 12 +++++++--- src/runtime/server/middleware/corsHandler.ts | 22 ++++++++++++++++++- src/types/middlewares.ts | 5 +++-- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/docs/content/1.documentation/3.middleware/4.cors-handler.md b/docs/content/1.documentation/3.middleware/4.cors-handler.md index f4a53280..e8cead21 100644 --- a/docs/content/1.documentation/3.middleware/4.cors-handler.md +++ b/docs/content/1.documentation/3.middleware/4.cors-handler.md @@ -48,8 +48,9 @@ You can also disable the middleware globally or per route by setting `corsHandle CORS handler accepts following configuration options: ```ts -interface H3CorsOptions { - origin?: '*' | 'null' | (string | RegExp)[] | ((origin: string) => boolean); +interface CorsOptions = { + origin?: '*' | string | string[]; + useRegExp?: boolean; methods?: '*' | HTTPMethod[]; allowHeaders?: '*' | string[]; exposeHeaders?: '*' | string[]; @@ -65,7 +66,12 @@ interface H3CorsOptions { - Default: `${serverUrl}` -The Access-Control-Allow-Origin response header indicates whether the response can be shared with requesting code from the given origin. +The Access-Control-Allow-Origin response header indicates whether the response can be shared with requesting code from the given origin. Use `'*'` to allow all origins. You can pass a single origin, or a list of origins. + +### `useRegExp` + +Set to `true` to parse all origin values into a regular expression using `new RegExp(origin, 'i')`. +You cannot use RegExp instances directly as origin values, because the nuxt config needs to be serializable. ### `methods` diff --git a/src/runtime/server/middleware/corsHandler.ts b/src/runtime/server/middleware/corsHandler.ts index 5b7fbb87..3406ab7c 100644 --- a/src/runtime/server/middleware/corsHandler.ts +++ b/src/runtime/server/middleware/corsHandler.ts @@ -7,7 +7,27 @@ export default defineEventHandler((event) => { if (rules.enabled && rules.corsHandler) { const { corsHandler } = rules - handleCors(event, corsHandler as H3CorsOptions) + + let origin: H3CorsOptions['origin'] + if (typeof corsHandler.origin === 'string') { + origin = [corsHandler.origin] + } else { + origin = corsHandler.origin + } + + if (origin && corsHandler.useRegExp) { + origin = origin.map((o) => new RegExp(o)) + } + + handleCors(event, { + origin, + methods: corsHandler.methods, + allowHeaders: corsHandler.allowHeaders, + exposeHeaders: corsHandler.exposeHeaders, + credentials: corsHandler.credentials, + maxAge: corsHandler.maxAge, + preflight: corsHandler.preflight + }) } }) diff --git a/src/types/middlewares.ts b/src/types/middlewares.ts index a5207a78..7359aaa6 100644 --- a/src/types/middlewares.ts +++ b/src/types/middlewares.ts @@ -46,9 +46,10 @@ export type BasicAuth = { export type HTTPMethod = 'GET' | 'POST' | 'DELETE' | 'PATCH' | 'PUT' | 'TRACE' | 'OPTIONS' | 'CONNECT' | 'HEAD'; -// Cannot use the H3CorsOptions from `h3` as it breaks the build process for some reason :( +// Cannot use the H3CorsOptions, because it allows unserializable types, such as functions or RegExp. export type CorsOptions = { - origin?: '*' | 'null' | string | (string | RegExp)[] | ((origin: string) => boolean); + origin?: '*' | string | string[]; + useRegExp?: boolean; methods?: '*' | HTTPMethod[]; allowHeaders?: '*' | string[]; exposeHeaders?: '*' | string[]; From 2763f72cd1eeb5514101a17f355802c18f525c57 Mon Sep 17 00:00:00 2001 From: Pascal Sthamer <10992664+P4sca1@users.noreply.github.com> Date: Fri, 9 Aug 2024 18:29:49 +0200 Subject: [PATCH 17/24] Add test cases for CORS Signed-off-by: Pascal Sthamer <10992664+P4sca1@users.noreply.github.com> --- test/cors.test.ts | 82 ++++++++++++++++++++++ test/fixtures/cors/.nuxtrc | 1 + test/fixtures/cors/app.vue | 5 ++ test/fixtures/cors/nuxt.config.ts | 53 ++++++++++++++ test/fixtures/cors/package.json | 5 ++ test/fixtures/cors/pages/index.vue | 3 + test/fixtures/cors/pages/multi.vue | 3 + test/fixtures/cors/pages/regexp-multi.vue | 3 + test/fixtures/cors/pages/regexp-single.vue | 3 + test/fixtures/cors/pages/single.vue | 3 + test/fixtures/cors/pages/star.vue | 4 ++ 11 files changed, 165 insertions(+) create mode 100644 test/cors.test.ts create mode 100644 test/fixtures/cors/.nuxtrc create mode 100644 test/fixtures/cors/app.vue create mode 100644 test/fixtures/cors/nuxt.config.ts create mode 100644 test/fixtures/cors/package.json create mode 100644 test/fixtures/cors/pages/index.vue create mode 100644 test/fixtures/cors/pages/multi.vue create mode 100644 test/fixtures/cors/pages/regexp-multi.vue create mode 100644 test/fixtures/cors/pages/regexp-single.vue create mode 100644 test/fixtures/cors/pages/single.vue create mode 100644 test/fixtures/cors/pages/star.vue diff --git a/test/cors.test.ts b/test/cors.test.ts new file mode 100644 index 00000000..7cae84ae --- /dev/null +++ b/test/cors.test.ts @@ -0,0 +1,82 @@ +import { describe, it, expect } from 'vitest' +import { fileURLToPath } from 'node:url' +import { setup, fetch } from '@nuxt/test-utils' + +describe('[nuxt-security] CORS', async () => { + await setup({ + rootDir: fileURLToPath(new URL('./fixtures/cors', import.meta.url)), + }) + + it ('should allow requests from serverUrl by default', async () => { + const res = await fetch('/', { headers: { origin: 'http://localhost:3000' } }) + expect(res.headers.get('Access-Control-Allow-Origin')).toBe('http://localhost:3000') + }) + + it ('should block requests from other origins by default', async () => { + const res = await fetch('/', { headers: { origin: 'http://example.com' } }) + expect(res.headers.get('Access-Control-Allow-Origin')).toBeNull() + }) + + it('should allow requests from all origins when * is set', async () => { + let res = await fetch('/star', { headers: { origin: 'http://example.com' } }) + expect(res.headers.get('Access-Control-Allow-Origin')).toBe('*') + + res = await fetch('/star', { headers: { origin: 'http://a.b.c.example.com' } }) + expect(res.headers.get('Access-Control-Allow-Origin')).toBe('*') + }) + + it('should allow requests if origin matches', async () => { + const res = await fetch('/single', { headers: { origin: 'https://example.com' } }) + expect(res.headers.get('Access-Control-Allow-Origin')).toBe('https://example.com') + }) + + it('should block requests when origin does not match', async () => { + const res = await fetch('/single', { headers: { origin: 'https://foo.example.com' } }) + expect(res.headers.get('Access-Control-Allow-Origin')).toBeNull() + }) + + it('should support multiple origins', async () => { + let res = await fetch('/multi', { headers: { origin: 'https://a.example.com' } }) + expect(res.headers.get('Access-Control-Allow-Origin')).toBe('https://a.example.com') + + res = await fetch('/multi', { headers: { origin: 'https://b.example.com' } }) + expect(res.headers.get('Access-Control-Allow-Origin')).toBe('https://b.example.com') + + res = await fetch('/multi', { headers: { origin: 'https://c.example.com' } }) + expect(res.headers.get('Access-Control-Allow-Origin')).toBeNull() + }) + + it('should support regular expressions', async () => { + let res = await fetch('/regexp-single', { headers: { origin: 'https://a.example.com' } }) + expect(res.headers.get('Access-Control-Allow-Origin')).toBe('https://a.example.com') + + res = await fetch('/regexp-single', { headers: { origin: 'https://b.example.com' } }) + expect(res.headers.get('Access-Control-Allow-Origin')).toBe('https://b.example.com') + + res = await fetch('/regexp-single', { headers: { origin: 'https://c.example.com' } }) + expect(res.headers.get('Access-Control-Allow-Origin')).toBeNull() + }) + + it('should support multiple regular expressions', async () => { + let res = await fetch('/regexp-multi', { headers: { origin: 'https://a.example.com' } }) + expect(res.headers.get('Access-Control-Allow-Origin')).toBe('https://a.example.com') + + res = await fetch('/regexp-multi', { headers: { origin: 'https://b.example.com' } }) + expect(res.headers.get('Access-Control-Allow-Origin')).toBe('https://b.example.com') + + res = await fetch('/regexp-multi', { headers: { origin: 'https://c.example.com' } }) + expect(res.headers.get('Access-Control-Allow-Origin')).toBeNull() + + res = await fetch('/regexp-multi', { headers: { origin: 'https://c.example.com' } }) + expect(res.headers.get('Access-Control-Allow-Origin')).toBeNull() + + res = await fetch('/regexp-multi', { headers: { origin: 'https://foo.example.com' } }) + expect(res.headers.get('Access-Control-Allow-Origin')).toBeNull() + + res = await fetch('/regexp-multi', { headers: { origin: 'https://1.foo.example.com' } }) + expect(res.headers.get('Access-Control-Allow-Origin')).toBe('https://1.foo.example.com') + + res = await fetch('/regexp-multi', { headers: { origin: 'https://a.b.c.foo.example.com' } }) + expect(res.headers.get('Access-Control-Allow-Origin')).toBe('https://a.b.c.foo.example.com') + }) +}) diff --git a/test/fixtures/cors/.nuxtrc b/test/fixtures/cors/.nuxtrc new file mode 100644 index 00000000..3c8c6a11 --- /dev/null +++ b/test/fixtures/cors/.nuxtrc @@ -0,0 +1 @@ +imports.autoImport=true \ No newline at end of file diff --git a/test/fixtures/cors/app.vue b/test/fixtures/cors/app.vue new file mode 100644 index 00000000..2b1be090 --- /dev/null +++ b/test/fixtures/cors/app.vue @@ -0,0 +1,5 @@ + diff --git a/test/fixtures/cors/nuxt.config.ts b/test/fixtures/cors/nuxt.config.ts new file mode 100644 index 00000000..c5581ea1 --- /dev/null +++ b/test/fixtures/cors/nuxt.config.ts @@ -0,0 +1,53 @@ +export default defineNuxtConfig({ + modules: [ + '../../../src/module' + ], + security: { + }, + routeRules: { + '/empty': { + security: { + corsHandler: { + origin: '' + } + } + }, + '/star': { + security: { + corsHandler: { + origin: '*' + } + } + }, + '/single': { + security: { + corsHandler: { + origin: 'https://example.com' + } + } + }, + '/multi': { + security: { + corsHandler: { + origin: ['https://a.example.com', 'https://b.example.com'] + } + } + }, + '/regexp-single': { + security: { + corsHandler: { + origin: '(a|b)\\.example\\.com', + useRegExp: true + } + } + }, + '/regexp-multi': { + security: { + corsHandler: { + origin: ['(a|b)\.example\.com', '(.*)\\.foo.example\\.com'], + useRegExp: true + } + } + }, + } +}) diff --git a/test/fixtures/cors/package.json b/test/fixtures/cors/package.json new file mode 100644 index 00000000..decd4334 --- /dev/null +++ b/test/fixtures/cors/package.json @@ -0,0 +1,5 @@ +{ + "private": true, + "name": "basic", + "type": "module" +} diff --git a/test/fixtures/cors/pages/index.vue b/test/fixtures/cors/pages/index.vue new file mode 100644 index 00000000..8371b274 --- /dev/null +++ b/test/fixtures/cors/pages/index.vue @@ -0,0 +1,3 @@ + diff --git a/test/fixtures/cors/pages/multi.vue b/test/fixtures/cors/pages/multi.vue new file mode 100644 index 00000000..7ccd8292 --- /dev/null +++ b/test/fixtures/cors/pages/multi.vue @@ -0,0 +1,3 @@ + diff --git a/test/fixtures/cors/pages/regexp-multi.vue b/test/fixtures/cors/pages/regexp-multi.vue new file mode 100644 index 00000000..0bc7574a --- /dev/null +++ b/test/fixtures/cors/pages/regexp-multi.vue @@ -0,0 +1,3 @@ + diff --git a/test/fixtures/cors/pages/regexp-single.vue b/test/fixtures/cors/pages/regexp-single.vue new file mode 100644 index 00000000..6e248fda --- /dev/null +++ b/test/fixtures/cors/pages/regexp-single.vue @@ -0,0 +1,3 @@ + diff --git a/test/fixtures/cors/pages/single.vue b/test/fixtures/cors/pages/single.vue new file mode 100644 index 00000000..4ef807ec --- /dev/null +++ b/test/fixtures/cors/pages/single.vue @@ -0,0 +1,3 @@ + diff --git a/test/fixtures/cors/pages/star.vue b/test/fixtures/cors/pages/star.vue new file mode 100644 index 00000000..3ea8861a --- /dev/null +++ b/test/fixtures/cors/pages/star.vue @@ -0,0 +1,4 @@ + + \ No newline at end of file From be68db2b1800c8acdaf5b1f3bd65e7ac4b4e154b Mon Sep 17 00:00:00 2001 From: Pascal Sthamer <10992664+P4sca1@users.noreply.github.com> Date: Fri, 9 Aug 2024 18:30:09 +0200 Subject: [PATCH 18/24] Add regexp example to docs and warn about escaping dots Signed-off-by: Pascal Sthamer <10992664+P4sca1@users.noreply.github.com> --- docs/content/1.documentation/3.middleware/4.cors-handler.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/content/1.documentation/3.middleware/4.cors-handler.md b/docs/content/1.documentation/3.middleware/4.cors-handler.md index e8cead21..f34155a9 100644 --- a/docs/content/1.documentation/3.middleware/4.cors-handler.md +++ b/docs/content/1.documentation/3.middleware/4.cors-handler.md @@ -72,6 +72,10 @@ The Access-Control-Allow-Origin response header indicates whether the response c Set to `true` to parse all origin values into a regular expression using `new RegExp(origin, 'i')`. You cannot use RegExp instances directly as origin values, because the nuxt config needs to be serializable. +When using regular expressions, make sure to escape dots in origins correctly. Otherwise a dot will match every character. + +The following matches `https://1.foo.example.com`, `https://a.b.c.foo.example.com`, but not `https://foo.example.com`. +`'(.*)\\.foo.example\\.com'` ### `methods` From 1f70e88cbec29184c1e7649fef182a7a77af416c Mon Sep 17 00:00:00 2001 From: Pascal Sthamer <10992664+P4sca1@users.noreply.github.com> Date: Fri, 9 Aug 2024 18:30:27 +0200 Subject: [PATCH 19/24] Pass origin * as is Signed-off-by: Pascal Sthamer <10992664+P4sca1@users.noreply.github.com> --- src/runtime/server/middleware/corsHandler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/runtime/server/middleware/corsHandler.ts b/src/runtime/server/middleware/corsHandler.ts index 3406ab7c..28e0701f 100644 --- a/src/runtime/server/middleware/corsHandler.ts +++ b/src/runtime/server/middleware/corsHandler.ts @@ -9,13 +9,13 @@ export default defineEventHandler((event) => { const { corsHandler } = rules let origin: H3CorsOptions['origin'] - if (typeof corsHandler.origin === 'string') { + if (typeof corsHandler.origin === 'string' && corsHandler.origin !== '*') { origin = [corsHandler.origin] } else { origin = corsHandler.origin } - if (origin && corsHandler.useRegExp) { + if (origin && origin !== '*' && corsHandler.useRegExp) { origin = origin.map((o) => new RegExp(o)) } From 2151b7d2bded5606ffaf74b52526cf234abe6a99 Mon Sep 17 00:00:00 2001 From: Pascal Sthamer <10992664+P4sca1@users.noreply.github.com> Date: Fri, 9 Aug 2024 18:33:10 +0200 Subject: [PATCH 20/24] Fix linting issues Signed-off-by: Pascal Sthamer <10992664+P4sca1@users.noreply.github.com> --- test/fixtures/cors/nuxt.config.ts | 2 ++ test/fixtures/cors/pages/star.vue | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/test/fixtures/cors/nuxt.config.ts b/test/fixtures/cors/nuxt.config.ts index c5581ea1..0005cab2 100644 --- a/test/fixtures/cors/nuxt.config.ts +++ b/test/fixtures/cors/nuxt.config.ts @@ -36,6 +36,7 @@ export default defineNuxtConfig({ '/regexp-single': { security: { corsHandler: { + // eslint-disable-next-line no-useless-escape -- This is parsed as a regular expression, so the escape is required. origin: '(a|b)\\.example\\.com', useRegExp: true } @@ -44,6 +45,7 @@ export default defineNuxtConfig({ '/regexp-multi': { security: { corsHandler: { + // eslint-disable-next-line no-useless-escape -- This is parsed as a regular expression, so the escape is required. origin: ['(a|b)\.example\.com', '(.*)\\.foo.example\\.com'], useRegExp: true } diff --git a/test/fixtures/cors/pages/star.vue b/test/fixtures/cors/pages/star.vue index 3ea8861a..f932b8a4 100644 --- a/test/fixtures/cors/pages/star.vue +++ b/test/fixtures/cors/pages/star.vue @@ -1,4 +1,3 @@ - \ No newline at end of file +
star
+ \ No newline at end of file From 6a041285cb688064725f3f7f5ea51cce71a1f5b1 Mon Sep 17 00:00:00 2001 From: Pascal Sthamer <10992664+P4sca1@users.noreply.github.com> Date: Fri, 9 Aug 2024 18:40:07 +0200 Subject: [PATCH 21/24] origin matching should be case insensitive Signed-off-by: Pascal Sthamer <10992664+P4sca1@users.noreply.github.com> --- src/runtime/server/middleware/corsHandler.ts | 2 +- test/cors.test.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/runtime/server/middleware/corsHandler.ts b/src/runtime/server/middleware/corsHandler.ts index 28e0701f..fd8b88ab 100644 --- a/src/runtime/server/middleware/corsHandler.ts +++ b/src/runtime/server/middleware/corsHandler.ts @@ -16,7 +16,7 @@ export default defineEventHandler((event) => { } if (origin && origin !== '*' && corsHandler.useRegExp) { - origin = origin.map((o) => new RegExp(o)) + origin = origin.map((o) => new RegExp(o, 'i')) } handleCors(event, { diff --git a/test/cors.test.ts b/test/cors.test.ts index 7cae84ae..31ff2365 100644 --- a/test/cors.test.ts +++ b/test/cors.test.ts @@ -57,6 +57,11 @@ describe('[nuxt-security] CORS', async () => { expect(res.headers.get('Access-Control-Allow-Origin')).toBeNull() }) + it('should match origins with regular expressions in a case-insensitive way', async () => { + const res = await fetch('/regexp-single', { headers: { origin: 'https://A.EXAMPLE.COM' } }) + expect(res.headers.get('Access-Control-Allow-Origin')).toBe('https://A.EXAMPLE.COM') + }) + it('should support multiple regular expressions', async () => { let res = await fetch('/regexp-multi', { headers: { origin: 'https://a.example.com' } }) expect(res.headers.get('Access-Control-Allow-Origin')).toBe('https://a.example.com') From f613df51d9b5e427c87287054bf42627025b34ef Mon Sep 17 00:00:00 2001 From: Thibault Vlacich Date: Mon, 9 Sep 2024 08:31:19 +0000 Subject: [PATCH 22/24] fix: update to latest @nuxt/module-builder --- package.json | 2 +- yarn.lock | 96 ++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 83 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 6a441bdd..68a61d6f 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ }, "devDependencies": { "@nuxt/eslint-config": "^0.3.10", - "@nuxt/module-builder": "^0.6.0", + "@nuxt/module-builder": "^0.8.3", "@nuxt/schema": "^3.11.2", "@nuxt/test-utils": "^3.12.0", "@types/node": "^18.18.1", diff --git a/yarn.lock b/yarn.lock index 456ed876..f96a3ea6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1128,17 +1128,19 @@ unimport "^3.7.1" untyped "^1.4.2" -"@nuxt/module-builder@^0.6.0": - version "0.6.0" - resolved "https://registry.yarnpkg.com/@nuxt/module-builder/-/module-builder-0.6.0.tgz#9510e419ab570bf710cb9970dbeea599c79321d8" - integrity sha512-d/sn+6n23qB+yGuItNvGnNlPpDzwcsW6riyISdo4H2MO/3TWFsIzB5+JZK104t0G6ftxB71xWHmBBYEdkXOhVw== +"@nuxt/module-builder@^0.8.3": + version "0.8.3" + resolved "https://registry.yarnpkg.com/@nuxt/module-builder/-/module-builder-0.8.3.tgz#d87312aedd9a025d297f34338d7198652b79fbfa" + integrity sha512-m9W3P6f6TFnHmVFKRo/2gELWDi3r0k8i93Z1fY5z410GZmttGVPv8KgRgOgC79agRi/OtpbyG3BPRaWdbDZa5w== dependencies: citty "^0.1.6" consola "^3.2.3" defu "^6.1.4" - mlly "^1.6.1" + magic-regexp "^0.8.0" + mlly "^1.7.1" pathe "^1.1.2" - pkg-types "^1.1.0" + pkg-types "^1.1.3" + tsconfck "^3.1.1" unbuild "^2.0.0" "@nuxt/schema@3.11.2", "@nuxt/schema@^3.10.3", "@nuxt/schema@^3.11.1", "@nuxt/schema@^3.11.2": @@ -4624,6 +4626,19 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +magic-regexp@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/magic-regexp/-/magic-regexp-0.8.0.tgz#c67de16456522a83672c22aa408b774facfd882e" + integrity sha512-lOSLWdE156csDYwCTIGiAymOLN7Epu/TU5e/oAnISZfU6qP+pgjkE+xbVjVn3yLPKN8n1G2yIAYTAM5KRk6/ow== + dependencies: + estree-walker "^3.0.3" + magic-string "^0.30.8" + mlly "^1.6.1" + regexp-tree "^0.1.27" + type-level-regexp "~0.1.17" + ufo "^1.4.0" + unplugin "^1.8.3" + magic-string-ast@^0.3.0: version "0.3.0" resolved "https://registry.npmjs.org/magic-string-ast/-/magic-string-ast-0.3.0.tgz" @@ -4875,6 +4890,16 @@ mlly@^1.2.0, mlly@^1.3.0, mlly@^1.4.0, mlly@^1.4.2, mlly@^1.6.1: pkg-types "^1.0.3" ufo "^1.3.2" +mlly@^1.7.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.7.1.tgz#e0336429bb0731b6a8e887b438cbdae522c8f32f" + integrity sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA== + dependencies: + acorn "^8.11.3" + pathe "^1.1.2" + pkg-types "^1.1.1" + ufo "^1.5.3" + mri@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz" @@ -5584,13 +5609,13 @@ pkg-types@^1.0.3: mlly "^1.2.0" pathe "^1.1.0" -pkg-types@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.1.0.tgz#3ec1bf33379030fd0a34c227b6c650e8ea7ca271" - integrity sha512-/RpmvKdxKf8uILTtoOhAgf30wYbP2Qw+L9p3Rvshx1JZVX+XQNZQFjlbmGHEGIm4CkVPlSn+NXmIM8+9oWQaSA== +pkg-types@^1.1.1, pkg-types@^1.1.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.2.0.tgz#d0268e894e93acff11a6279de147e83354ebd42d" + integrity sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA== dependencies: confbox "^0.1.7" - mlly "^1.6.1" + mlly "^1.7.1" pathe "^1.1.2" pluralize@^8.0.0: @@ -6458,7 +6483,16 @@ streamx@^2.15.0: optionalDependencies: bare-events "^2.2.0" -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -6490,7 +6524,14 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -6688,6 +6729,11 @@ ts-api-utils@^1.0.1, ts-api-utils@^1.3.0: resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz" integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== +tsconfck@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-3.1.3.tgz#a8202f51dab684c426314796cdb0bbd0fe0cdf80" + integrity sha512-ulNZP1SVpRDesxeMLON/LtWM8HIgAJEIVpVVhBM6gsmvQ8+Rh+ZG7FWGvHh7Ah3pRABwVJWklWCr/BTZSv0xnQ== + tslib@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" @@ -6739,6 +6785,11 @@ type-fest@^3.8.0: resolved "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz" integrity sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g== +type-level-regexp@~0.1.17: + version "0.1.17" + resolved "https://registry.yarnpkg.com/type-level-regexp/-/type-level-regexp-0.1.17.tgz#ec1bf7dd65b85201f9863031d6f023bdefc2410f" + integrity sha512-wTk4DH3cxwk196uGLK/E9pE45aLfeKJacKmcEgEOA/q5dnPGNxXt0cfYdFxb57L+sEpf1oJH4Dnx/pnRcku9jg== + typescript@^5.4.5: version "5.4.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" @@ -6932,6 +6983,14 @@ unplugin@^1.12.0: webpack-sources "^3.2.3" webpack-virtual-modules "^0.6.2" +unplugin@^1.8.3: + version "1.13.1" + resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-1.13.1.tgz#d33e338374bfb80755a3789ed7de25b8f006131c" + integrity sha512-6Kq1iSSwg7KyjcThRUks9LuqDAKvtnioxbL9iEtB9ctTyBA5OmrB8gZd/d225VJu1w3UpUsKV7eGrvf59J7+VA== + dependencies: + acorn "^8.12.1" + webpack-virtual-modules "^0.6.2" + unstorage@^1.10.2: version "1.10.2" resolved "https://registry.npmjs.org/unstorage/-/unstorage-1.10.2.tgz" @@ -7321,7 +7380,16 @@ wide-align@^1.1.2: dependencies: string-width "^1.0.2 || 2 || 3 || 4" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== From 3a5e3bfc1438d16a908f74cf1b692a90663d37cd Mon Sep 17 00:00:00 2001 From: Thibault Vlacich Date: Thu, 12 Sep 2024 13:03:28 +0200 Subject: [PATCH 23/24] fix: augment @nuxt/schema rather than nuxt/schema --- src/types/module.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/types/module.ts b/src/types/module.ts index e2085e23..6547771e 100644 --- a/src/types/module.ts +++ b/src/types/module.ts @@ -38,7 +38,7 @@ export type NuxtSecurityRouteRules = Partial< & { requestSizeLimiter: RequestSizeLimiter | false } > -declare module 'nuxt/schema' { +declare module '@nuxt/schema' { interface NuxtOptions { security: ModuleOptions } @@ -51,12 +51,6 @@ declare module 'nuxt/schema' { } } -declare module '@nuxt/schema' { - interface NuxtHooks { - 'nuxt-security:prerenderedHeaders': (prerenderedHeaders: Record>) => HookResult - } -} - declare module 'nitropack' { interface NitroRouteConfig { security?: NuxtSecurityRouteRules; From 4c577d10549da01f5808baf48ecf580da0e060f3 Mon Sep 17 00:00:00 2001 From: Baroshem Date: Thu, 19 Sep 2024 12:58:39 +0200 Subject: [PATCH 24/24] chore: bump to 2.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 68a61d6f..93c01330 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nuxt-security", - "version": "2.0.0-rc.9", + "version": "2.0.0", "license": "MIT", "type": "module", "homepage": "https://nuxt-security.vercel.app",