From 9f5141e5d19c4a5a0559970b880fde142b9cb98c Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 17 Mar 2026 17:02:42 -0700 Subject: [PATCH 1/2] feat(csp): allow chat UI to be embedded in iframes Mirror the existing form embed CSP pattern for chat pages: add getChatEmbedCSPPolicy() with frame-ancestors *, configure /chat/:path* headers in next.config.ts without X-Frame-Options, and early-return in proxy.ts so chat routes skip the strict runtime CSP. Co-Authored-By: Claude Opus 4.6 --- apps/sim/lib/core/security/csp.ts | 12 ++++++++++++ apps/sim/next.config.ts | 25 ++++++++++++++++++++++--- apps/sim/proxy.ts | 7 ++----- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/apps/sim/lib/core/security/csp.ts b/apps/sim/lib/core/security/csp.ts index b1148998dd4..f6d16e78633 100644 --- a/apps/sim/lib/core/security/csp.ts +++ b/apps/sim/lib/core/security/csp.ts @@ -201,6 +201,18 @@ export function getWorkflowExecutionCSPPolicy(): string { return "default-src * 'unsafe-inline' 'unsafe-eval'; connect-src *;" } +/** + * CSP for embeddable chat pages + * Allows embedding in iframes from any origin while maintaining other security policies + */ +export function getChatEmbedCSPPolicy(): string { + const basePolicy = buildCSPString({ + ...buildTimeCSPDirectives, + 'frame-ancestors': ['*'], + }) + return basePolicy +} + /** * CSP for embeddable form pages * Allows embedding in iframes from any origin while maintaining other security policies 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()) } From 154c5f5e6f086bafc33970d67dc1c4c82894e09e Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 17 Mar 2026 17:11:41 -0700 Subject: [PATCH 2/2] refactor(csp): extract shared getEmbedCSPPolicy helper Deduplicate getChatEmbedCSPPolicy and getFormEmbedCSPPolicy into a shared private helper to prevent future divergence. Co-Authored-By: Claude Opus 4.6 --- apps/sim/lib/core/security/csp.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/apps/sim/lib/core/security/csp.ts b/apps/sim/lib/core/security/csp.ts index f6d16e78633..c5dde9ef41d 100644 --- a/apps/sim/lib/core/security/csp.ts +++ b/apps/sim/lib/core/security/csp.ts @@ -202,27 +202,28 @@ export function getWorkflowExecutionCSPPolicy(): string { } /** - * CSP for embeddable chat pages + * Shared CSP for embeddable pages (chat, forms) * Allows embedding in iframes from any origin while maintaining other security policies */ -export function getChatEmbedCSPPolicy(): 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 - * Allows embedding in iframes from any origin while maintaining other security policies */ export function getFormEmbedCSPPolicy(): string { - const basePolicy = buildCSPString({ - ...buildTimeCSPDirectives, - 'frame-ancestors': ['*'], - }) - return basePolicy + return getEmbedCSPPolicy() } /**