From 035e86b6aeb84000fd31f5d0ba56cbbe91e0c8e2 Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Tue, 18 Mar 2025 14:47:51 -0600 Subject: [PATCH] x frame options update --- web/src/middleware/cspPolicyMiddleware.ts | 47 +++++++++++++++++++--- web/src/middleware/publicPageMiddleware.ts | 7 ++++ 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/web/src/middleware/cspPolicyMiddleware.ts b/web/src/middleware/cspPolicyMiddleware.ts index c815dc6ef..a7b123888 100644 --- a/web/src/middleware/cspPolicyMiddleware.ts +++ b/web/src/middleware/cspPolicyMiddleware.ts @@ -1,6 +1,7 @@ import { NextRequest } from 'next/server'; +import { isEmbedPage } from './publicPageMiddleware'; -const cspHeader = { +const defaultCspHeader = { 'Content-Security-Policy': [ // Default directives "default-src 'self'", @@ -12,7 +13,7 @@ const cspHeader = { "img-src 'self' blob: data: https://*.vercel.app https://*.supabase.co", // Fonts "font-src 'self' https://fonts.gstatic.com", - // Frame ancestors + // Frame ancestors - no embedding for non-embed routes "frame-ancestors 'none'", // Connect sources for API calls "connect-src 'self' https://*.vercel.app https://*.supabase.co wss://*.supabase.co", @@ -29,12 +30,48 @@ const cspHeader = { ].join('; ') }; +const embedCspHeader = { + 'Content-Security-Policy': [ + // Default directives + "default-src 'self'", + // Scripts + "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://vercel.live https://*.vercel.app", + // Styles + "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com", + // Images + "img-src 'self' blob: data: https://*.vercel.app https://*.supabase.co", + // Fonts + "font-src 'self' https://fonts.gstatic.com", + // Frame ancestors - allow embedding from any domain for /embed routes + 'frame-ancestors *', + // Connect sources for API calls + "connect-src 'self' https://*.vercel.app https://*.supabase.co wss://*.supabase.co", + // Media + "media-src 'self'", + // Object + "object-src 'none'", + // Form actions + "form-action 'self'", + // Base URI + "base-uri 'self'", + // Manifest + "manifest-src 'self'" + ].join('; ') +}; + export const cspPolicyMiddleware = (request: NextRequest) => { - // Add CSP headers - request.headers.set('Content-Security-Policy', cspHeader['Content-Security-Policy']); + const isEmbedRoute = isEmbedPage(request); + + // Add CSP headers based on route + request.headers.set( + 'Content-Security-Policy', + isEmbedRoute + ? embedCspHeader['Content-Security-Policy'] + : defaultCspHeader['Content-Security-Policy'] + ); // Add additional security headers - request.headers.set('X-Frame-Options', 'SAMEORIGIN'); + if (!isEmbedRoute) request.headers.set('X-Frame-Options', 'DENY'); request.headers.set('X-Content-Type-Options', 'nosniff'); request.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin'); diff --git a/web/src/middleware/publicPageMiddleware.ts b/web/src/middleware/publicPageMiddleware.ts index f18eb388e..e35c4f6c6 100644 --- a/web/src/middleware/publicPageMiddleware.ts +++ b/web/src/middleware/publicPageMiddleware.ts @@ -29,3 +29,10 @@ export const assetPermissionCheck = (request: NextRequest): boolean => { const matchedRoute = createPathnameToBusterRoute(route); return assetCheckPages.includes(matchedRoute); }; + +const embedPages: BusterRoutes[] = Object.values(BusterEmbedRoutes); +export const isEmbedPage = (request: NextRequest): boolean => { + const route = request.nextUrl.pathname; + const matchedRoute = createPathnameToBusterRoute(route); + return embedPages.includes(matchedRoute); +};