From e5e987eb4cd7a533807988d861df64c19677b20d Mon Sep 17 00:00:00 2001 From: Leon Serfaty <80597822+silkoserfo@users.noreply.github.com> Date: Fri, 3 Jul 2026 07:37:35 -0400 Subject: [PATCH] fix(csp): whitelist Umami analytics origin so tracking works MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CSP script-src/connect-src didn't include the Umami host (fickanalytics.phluit.net), so the browser blocked both loading script.js and the event beacons (POST /api/send) — analytics recorded 0 visits despite the site being live. Add a umamiOrigin() helper (derived from NEXT_PUBLIC_UMAMI_SRC, defaulting to the shared phluit instance) and include it in script-src and connect-src. Co-Authored-By: Claude Opus 4.8 --- proxy.ts | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/proxy.ts b/proxy.ts index 1d81165..118018a 100644 --- a/proxy.ts +++ b/proxy.ts @@ -41,6 +41,19 @@ function sentryIngestOrigin(): string | null { } } +// Origin serving the Umami analytics script (script.js) and receiving its event +// beacons (POST /api/send). Mirrors the component default so the CSP allows both +// loading the script AND sending events; stays in sync with NEXT_PUBLIC_UMAMI_SRC +// when overridden. +function umamiOrigin(): string { + const src = process.env.NEXT_PUBLIC_UMAMI_SRC || "https://fickanalytics.phluit.net/script.js" + try { + return new URL(src).origin + } catch { + return "" + } +} + // Build the Content-Security-Policy. `script-src` uses 'unsafe-inline' because // Next.js 16's Turbopack build does NOT stamp a per-request nonce onto its // inline hydration scripts (`self.__next_f.push(...)`). A nonce-based policy @@ -54,13 +67,20 @@ function buildCsp(): string { // Dev additionally needs 'unsafe-eval' (Turbopack HMR) plus a dev websocket // (added to connect-src below). - const scriptSrc = isDev - ? `script-src 'self' 'unsafe-inline' 'unsafe-eval' https://challenges.cloudflare.com` - : `script-src 'self' 'unsafe-inline' https://challenges.cloudflare.com` + const umami = umamiOrigin() + const scriptSrc = [ + "script-src 'self' 'unsafe-inline'", + isDev ? "'unsafe-eval'" : "", + "https://challenges.cloudflare.com", + umami, // load the Umami analytics script + ] + .filter(Boolean) + .join(" ") const connectSrc = [ "connect-src 'self'", isDev ? "ws: wss:" : "", "https://api.stripe.com https://api.openai.com https://challenges.cloudflare.com", + umami, // Umami event beacons (POST /api/send) sentry ?? "", ] .filter(Boolean)