From a0107c1154c4437397570db079534fdbc9b82dcf Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Thu, 21 Nov 2024 13:19:05 -0700 Subject: [PATCH 1/2] fix(nosecone): Export overridden defaults from adapters --- nosecone-next/index.ts | 86 +++++++++---------------------------- nosecone-sveltekit/index.ts | 18 ++++---- 2 files changed, 30 insertions(+), 74 deletions(-) diff --git a/nosecone-next/index.ts b/nosecone-next/index.ts index d8ee72121..7cb1c5724 100644 --- a/nosecone-next/index.ts +++ b/nosecone-next/index.ts @@ -1,8 +1,23 @@ -import nosecone, { defaults } from "nosecone"; -import type { CspDirectives, NoseconeOptions } from "nosecone"; - -// Re-exports the defaults for easier overrides -export { defaults }; +import nosecone, { defaults as baseDefaults } from "nosecone"; +import type { NoseconeOptions } from "nosecone"; + +export const defaults = { + ...baseDefaults, + contentSecurityPolicy: { + directives: { + ...baseDefaults.contentSecurityPolicy.directives, + scriptSrc: + // Replace the defaults to remove `'self'` + process.env.NODE_ENV === "development" + ? ([nonce, "'strict-dynamic'"] as const) + : ([nonce, "'strict-dynamic'", "'unsafe-eval'"] as const), + styleSrc: [ + ...baseDefaults.contentSecurityPolicy.directives.styleSrc, + "'unsafe-inline'", + ], + }, + }, +} as const; // We export `nosecone` as the default so it can be used with `new Response()` export default nosecone; @@ -11,64 +26,6 @@ function nonce() { return `'nonce-${btoa(crypto.randomUUID())}'` as const; } -const defaultDirectives = defaults.contentSecurityPolicy.directives; - -function applyNextDefaults(options: NoseconeOptions): NoseconeOptions { - if ( - typeof options.contentSecurityPolicy === "undefined" || - !options.contentSecurityPolicy - ) { - return options; - } - - const directives = - options.contentSecurityPolicy === true || - typeof options.contentSecurityPolicy.directives === "undefined" - ? defaultDirectives - : options.contentSecurityPolicy.directives; - - let scriptSrc: CspDirectives["scriptSrc"]; - if (directives.scriptSrc === true) { - scriptSrc = defaultDirectives.scriptSrc; - } else { - scriptSrc = directives.scriptSrc; - } - if (scriptSrc) { - const scriptSrcSet = new Set(scriptSrc); - scriptSrcSet.delete("'self'"); - scriptSrcSet.add(nonce()); - scriptSrcSet.add("'strict-dynamic'"); - // Next.js hot reloading relies on `eval` so we enable it in development - if (process.env.NODE_ENV === "development") { - scriptSrcSet.add("'unsafe-eval'"); - } - scriptSrc = Array.from(scriptSrcSet); - } - - let styleSrc: CspDirectives["styleSrc"]; - if (directives.styleSrc === true) { - styleSrc = defaultDirectives.styleSrc; - } else { - styleSrc = directives.styleSrc; - } - if (styleSrc) { - const styleSrcSet = new Set(styleSrc); - styleSrcSet.add("'unsafe-inline'"); - styleSrc = Array.from(styleSrcSet); - } - - return { - ...options, - contentSecurityPolicy: { - directives: { - ...directives, - scriptSrc, - styleSrc, - }, - }, - }; -} - /** * Create Next.js middleware that sets secure headers on every request. * @@ -77,8 +34,7 @@ function applyNextDefaults(options: NoseconeOptions): NoseconeOptions { */ export function createMiddleware(options: NoseconeOptions = defaults) { return async () => { - const opts = applyNextDefaults(options); - const headers = nosecone(opts); + const headers = nosecone(options); return new Response(null, { headers: { diff --git a/nosecone-sveltekit/index.ts b/nosecone-sveltekit/index.ts index 627812ac7..8d38f7fa9 100644 --- a/nosecone-sveltekit/index.ts +++ b/nosecone-sveltekit/index.ts @@ -1,14 +1,19 @@ import nosecone, { CONTENT_SECURITY_POLICY_DIRECTIVES, QUOTED, - defaults, + defaults as baseDefaults, NoseconeValidationError, } from "nosecone"; import type { CspDirectives, NoseconeOptions } from "nosecone"; import type { Handle, KitConfig } from "@sveltejs/kit"; -// Re-exports the defaults for easier overrides -export { defaults }; +export const defaults = { + ...baseDefaults, + directives: { + ...baseDefaults.contentSecurityPolicy.directives, + scriptSrc: ["'strict-dynamic'"], + }, +} as const; // We export `nosecone` as the default so it can be used with `new Response()` export default nosecone; @@ -44,11 +49,6 @@ export type ContentSecurityPolicyConfig = { // TODO: Support `reportOnly` }; -const directives: CspDirectives = { - ...defaults.contentSecurityPolicy.directives, - scriptSrc: ["'strict-dynamic'"], -}; - function unquote(value?: string) { for (const [unquoted, quoted] of QUOTED) { if (value === quoted) { @@ -108,7 +108,7 @@ function directivesToSvelteKitConfig( } export function csp( - options: ContentSecurityPolicyConfig = { mode: "auto", directives }, + options: ContentSecurityPolicyConfig = { mode: "auto" }, ): SvelteKitCsp { return { mode: options.mode ? options.mode : "auto", From e0099597f74270ba4dc29db1c30a0242821086b9 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Thu, 21 Nov 2024 13:19:47 -0700 Subject: [PATCH 2/2] add comment --- nosecone-next/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nosecone-next/index.ts b/nosecone-next/index.ts index 7cb1c5724..d810e80e7 100644 --- a/nosecone-next/index.ts +++ b/nosecone-next/index.ts @@ -10,7 +10,8 @@ export const defaults = { // Replace the defaults to remove `'self'` process.env.NODE_ENV === "development" ? ([nonce, "'strict-dynamic'"] as const) - : ([nonce, "'strict-dynamic'", "'unsafe-eval'"] as const), + : // Next.js hot reloading relies on `eval` so we enable it in development + ([nonce, "'strict-dynamic'", "'unsafe-eval'"] as const), styleSrc: [ ...baseDefaults.contentSecurityPolicy.directives.styleSrc, "'unsafe-inline'",