diff --git a/src/proxy.ts b/src/proxy.ts index c6ed37c..a2811a0 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -53,6 +53,22 @@ function getSupabaseHostname(): string { return "*.supabase.co"; } +function getClerkFrontendApiHostname(): string { + // A Clerk publishable key is base64(`${frontendApiHost}$`) after its + // pk_test_/pk_live_ prefix — this middleware runs on the Edge runtime, so + // atob (not Buffer) for the decode. + const key = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY; + if (key) { + try { + const encoded = key.replace(/^pk_(test|live)_/, ""); + return atob(encoded).replace(/\$$/, ""); + } catch { + /* fall through */ + } + } + return "clerk.pawfeed.org"; +} + function getGlitchtipHostname(): string { const dsn = process.env.NEXT_PUBLIC_SENTRY_DSN; if (dsn) { @@ -154,7 +170,13 @@ export default clerkMiddleware( strict: true, reportTo: "/api/csp-report", directives: { - "script-src": ["unsafe-eval", "'report-sample'"], + // Clerk's own middleware adds its frontend API host to connect-src + // automatically, but NOT to script-src — without this, the initial + // (unversioned) clerk.browser.js/ui.browser.js load is blocked by + // 'strict-dynamic' before the 307 redirect to the versioned URL, + // producing two CSP violations (reported to /api/csp-report) on + // every page load that renders Clerk UI. + "script-src": [`https://${getClerkFrontendApiHostname()}`, "unsafe-eval", "'report-sample'"], "img-src": [`https://${getSupabaseHostname()}`, "https://image.mux.com", "blob:"], "connect-src": [ `https://${getSupabaseHostname()}`,