From 4a6d38caeda890544e240d915dfb073949403e40 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 31 Aug 2026 13:36:45 +0000 Subject: [PATCH 1/2] fix: allow Jam recorder in production CSP --- nginx.conf | 2 +- src/test/vercel-deployment-config.test.ts | 15 +++++++++++++++ vercel.json | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/nginx.conf b/nginx.conf index c8a165ca6..97771be37 100644 --- a/nginx.conf +++ b/nginx.conf @@ -8,7 +8,7 @@ server { gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; # Security headers - add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; img-src 'self' data: https:; font-src 'self' data: https://fonts.gstatic.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; script-src 'self'; frame-src 'self' https://accounts.google.com; connect-src 'self' https://uziwdtkzofptihahfooo.supabase.co https://*.supabase.co https://api.openai.com https://api.anthropic.com https://generativelanguage.googleapis.com https://r.jina.ai https://s.jina.ai https://oauth2.googleapis.com https://www.googleapis.com;" always; + add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; img-src 'self' data: https:; font-src 'self' data: https://fonts.gstatic.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; script-src 'self' https://*.jam.dev; frame-src 'self' https://accounts.google.com https://*.jam.dev; connect-src 'self' https://uziwdtkzofptihahfooo.supabase.co https://*.supabase.co https://api.openai.com https://api.anthropic.com https://generativelanguage.googleapis.com https://r.jina.ai https://s.jina.ai https://oauth2.googleapis.com https://www.googleapis.com;" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; diff --git a/src/test/vercel-deployment-config.test.ts b/src/test/vercel-deployment-config.test.ts index 5838e05e2..6c971836c 100644 --- a/src/test/vercel-deployment-config.test.ts +++ b/src/test/vercel-deployment-config.test.ts @@ -86,6 +86,21 @@ describe("Vercel frontend deployment configuration", () => { expect(themeBootstrap).toContain("debate-trainer-settings"); }); + it("allows the installed Jam scripts and recorder frames in production", () => { + const config = loadConfig(); + const csp = headersFor(config, "/(.*)")["Content-Security-Policy"]; + const indexHtml = readProjectFile("index.html"); + const nginxConfig = readProjectFile("nginx.conf"); + + expect(csp).toContain("script-src 'self' https://*.jam.dev"); + expect(csp).toContain("frame-src 'self' https://accounts.google.com https://*.jam.dev"); + expect(nginxConfig).toContain("script-src 'self' https://*.jam.dev"); + expect(nginxConfig).toContain("frame-src 'self' https://accounts.google.com https://*.jam.dev"); + expect(indexHtml).toContain('src="https://js.jam.dev/recorder.js"'); + expect(indexHtml).toContain('src="https://js.jam.dev/capture.js"'); + expect(indexHtml).toContain('meta name="jam:team"'); + }); + it("refreshes the offline app shell after successful navigation", () => { const serviceWorker = readProjectFile("public/sw.js"); expect(serviceWorker).toContain("cache.put('/index.html', response.clone())"); diff --git a/vercel.json b/vercel.json index d523f92f0..2b5a25871 100644 --- a/vercel.json +++ b/vercel.json @@ -15,7 +15,7 @@ "headers": [ { "key": "Content-Security-Policy", - "value": "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; img-src 'self' data: https:; font-src 'self' data: https://fonts.gstatic.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; script-src 'self'; frame-src 'self' https://accounts.google.com; connect-src 'self' https://uziwdtkzofptihahfooo.supabase.co https://*.supabase.co https://api.openai.com https://api.anthropic.com https://generativelanguage.googleapis.com https://r.jina.ai https://s.jina.ai https://oauth2.googleapis.com https://www.googleapis.com https://*.ingest.sentry.io https://*.sentry.io https://eu.i.posthog.com https://*.posthog.com;" + "value": "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; img-src 'self' data: https:; font-src 'self' data: https://fonts.gstatic.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; script-src 'self' https://*.jam.dev; frame-src 'self' https://accounts.google.com https://*.jam.dev; connect-src 'self' https://uziwdtkzofptihahfooo.supabase.co https://*.supabase.co https://api.openai.com https://api.anthropic.com https://generativelanguage.googleapis.com https://r.jina.ai https://s.jina.ai https://oauth2.googleapis.com https://www.googleapis.com https://*.ingest.sentry.io https://*.sentry.io https://eu.i.posthog.com https://*.posthog.com;" }, { "key": "X-Frame-Options", From 9b33ed5345df46f40dbfad98f73381fe33354169 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 31 Aug 2026 13:42:14 +0000 Subject: [PATCH 2/2] fix: keep production CSP policies aligned --- nginx.conf | 2 +- src/test/vercel-deployment-config.test.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/nginx.conf b/nginx.conf index 97771be37..d21da0c17 100644 --- a/nginx.conf +++ b/nginx.conf @@ -8,7 +8,7 @@ server { gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; # Security headers - add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; img-src 'self' data: https:; font-src 'self' data: https://fonts.gstatic.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; script-src 'self' https://*.jam.dev; frame-src 'self' https://accounts.google.com https://*.jam.dev; connect-src 'self' https://uziwdtkzofptihahfooo.supabase.co https://*.supabase.co https://api.openai.com https://api.anthropic.com https://generativelanguage.googleapis.com https://r.jina.ai https://s.jina.ai https://oauth2.googleapis.com https://www.googleapis.com;" always; + add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; img-src 'self' data: https:; font-src 'self' data: https://fonts.gstatic.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; script-src 'self' https://*.jam.dev; frame-src 'self' https://accounts.google.com https://*.jam.dev; connect-src 'self' https://uziwdtkzofptihahfooo.supabase.co https://*.supabase.co https://api.openai.com https://api.anthropic.com https://generativelanguage.googleapis.com https://r.jina.ai https://s.jina.ai https://oauth2.googleapis.com https://www.googleapis.com https://*.ingest.sentry.io https://*.sentry.io https://eu.i.posthog.com https://*.posthog.com;" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; diff --git a/src/test/vercel-deployment-config.test.ts b/src/test/vercel-deployment-config.test.ts index 6c971836c..14437754d 100644 --- a/src/test/vercel-deployment-config.test.ts +++ b/src/test/vercel-deployment-config.test.ts @@ -91,14 +91,14 @@ describe("Vercel frontend deployment configuration", () => { const csp = headersFor(config, "/(.*)")["Content-Security-Policy"]; const indexHtml = readProjectFile("index.html"); const nginxConfig = readProjectFile("nginx.conf"); + const nginxCsp = nginxConfig.match(/add_header Content-Security-Policy "([^"]+)"/)?.[1]; expect(csp).toContain("script-src 'self' https://*.jam.dev"); expect(csp).toContain("frame-src 'self' https://accounts.google.com https://*.jam.dev"); - expect(nginxConfig).toContain("script-src 'self' https://*.jam.dev"); - expect(nginxConfig).toContain("frame-src 'self' https://accounts.google.com https://*.jam.dev"); - expect(indexHtml).toContain('src="https://js.jam.dev/recorder.js"'); - expect(indexHtml).toContain('src="https://js.jam.dev/capture.js"'); - expect(indexHtml).toContain('meta name="jam:team"'); + expect(nginxCsp).toBe(csp); + expect(indexHtml).toContain(''); + expect(indexHtml).toContain(''); + expect(indexHtml).toContain(''); }); it("refreshes the offline app shell after successful navigation", () => {