From 2d4c3a57dad3d63518a9aac203a385cf1b84acf5 Mon Sep 17 00:00:00 2001 From: Catherine Renelle <32781029+catrenelle@users.noreply.github.com> Date: Mon, 9 Mar 2026 08:16:45 -0400 Subject: [PATCH] Replace unsafe-inline with CSP nonces for script-src SvelteKit now handles script-src with automatic nonces via its built-in CSP support. The hooks handler merges extra directives (style-src, font-src, img-src, etc.) onto SvelteKit's nonce-based policy for page responses, and sets a full CSP for API responses. Co-Authored-By: Claude Opus 4.6 --- src/hooks.server.ts | 12 +++++++++++- svelte.config.js | 6 ++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 76a5e57..9611821 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -100,7 +100,17 @@ export const handle: Handle = async ({ event, resolve }) => { response.headers.set('X-Content-Type-Options', 'nosniff'); response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin'); response.headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains'); - response.headers.set('Content-Security-Policy', "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' ws: wss:; frame-ancestors 'none'"); + // SvelteKit sets CSP with nonces on page responses via svelte.config.js. + // For non-page responses (API, etc.) or to add directives SvelteKit doesn't cover, set our own. + const existingCSP = response.headers.get('content-security-policy'); + if (existingCSP) { + // SvelteKit set a CSP with script-src nonce — append our extra directives + const extras = "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' ws: wss:; frame-ancestors 'none'"; + response.headers.set('Content-Security-Policy', `${existingCSP}; ${extras}`); + } else { + // Non-page response (API routes) — set full CSP without unsafe-inline + response.headers.set('Content-Security-Policy', "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' ws: wss:; frame-ancestors 'none'"); + } response.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()'); logger.info({ diff --git a/svelte.config.js b/svelte.config.js index eda5535..2cabbfe 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -8,6 +8,12 @@ const config = { $components: 'src/lib/components', $server: 'src/lib/server', $utils: 'src/lib/utils' + }, + csp: { + directives: { + 'default-src': ['self'], + 'script-src': ['self'] + } } } };