diff --git a/apps/sim/lib/core/security/csp.ts b/apps/sim/lib/core/security/csp.ts index b1148998dd4..c5dde9ef41d 100644 --- a/apps/sim/lib/core/security/csp.ts +++ b/apps/sim/lib/core/security/csp.ts @@ -202,15 +202,28 @@ export function getWorkflowExecutionCSPPolicy(): string { } /** - * CSP for embeddable form pages + * Shared CSP for embeddable pages (chat, forms) * Allows embedding in iframes from any origin while maintaining other security policies */ -export function getFormEmbedCSPPolicy(): string { - const basePolicy = buildCSPString({ +function getEmbedCSPPolicy(): string { + return buildCSPString({ ...buildTimeCSPDirectives, 'frame-ancestors': ['*'], }) - return basePolicy +} + +/** + * CSP for embeddable chat pages + */ +export function getChatEmbedCSPPolicy(): string { + return getEmbedCSPPolicy() +} + +/** + * CSP for embeddable form pages + */ +export function getFormEmbedCSPPolicy(): string { + return getEmbedCSPPolicy() } /** diff --git a/apps/sim/next.config.ts b/apps/sim/next.config.ts index e5975cc6e9a..1b23b9ec93d 100644 --- a/apps/sim/next.config.ts +++ b/apps/sim/next.config.ts @@ -2,6 +2,7 @@ import type { NextConfig } from 'next' import { env, getEnv, isTruthy } from './lib/core/config/env' import { isDev } from './lib/core/config/feature-flags' import { + getChatEmbedCSPPolicy, getFormEmbedCSPPolicy, getMainCSPPolicy, getWorkflowExecutionCSPPolicy, @@ -255,6 +256,24 @@ const nextConfig: NextConfig = { }, ], }, + // Chat pages - allow iframe embedding from any origin + { + source: '/chat/:path*', + headers: [ + { + key: 'X-Content-Type-Options', + value: 'nosniff', + }, + // No X-Frame-Options to allow iframe embedding + { + key: 'Content-Security-Policy', + value: getChatEmbedCSPPolicy(), + }, + // Permissive CORS for chat requests from embedded chats + { key: 'Cross-Origin-Embedder-Policy', value: 'unsafe-none' }, + { key: 'Cross-Origin-Opener-Policy', value: 'unsafe-none' }, + ], + }, // Form pages - allow iframe embedding from any origin { source: '/form/:path*', @@ -284,10 +303,10 @@ const nextConfig: NextConfig = { ], }, // Apply security headers to routes not handled by middleware runtime CSP - // Middleware handles: /, /workspace/*, /chat/* - // Exclude form routes which have their own permissive headers + // Middleware handles: /, /workspace/* + // Exclude chat and form routes which have their own permissive embed headers { - source: '/((?!workspace|chat$|form).*)', + source: '/((?!workspace|chat|form).*)', headers: [ { key: 'X-Content-Type-Options', diff --git a/apps/sim/proxy.ts b/apps/sim/proxy.ts index 36ada3484f1..acd93ebfa12 100644 --- a/apps/sim/proxy.ts +++ b/apps/sim/proxy.ts @@ -155,6 +155,7 @@ export async function proxy(request: NextRequest) { return response } + // Chat pages are publicly accessible embeds — CSP is set in next.config.ts headers if (url.pathname.startsWith('/chat/')) { return NextResponse.next() } @@ -188,11 +189,7 @@ export async function proxy(request: NextRequest) { const response = NextResponse.next() response.headers.set('Vary', 'User-Agent') - if ( - url.pathname.startsWith('/workspace') || - url.pathname.startsWith('/chat') || - url.pathname === '/' - ) { + if (url.pathname.startsWith('/workspace') || url.pathname === '/') { response.headers.set('Content-Security-Policy', generateRuntimeCSP()) }