Skip to content

Commit 9f5141e

Browse files
waleedlatif1claude
andcommitted
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 <noreply@anthropic.com>
1 parent 75a3e2c commit 9f5141e

File tree

3 files changed

+36
-8
lines changed

3 files changed

+36
-8
lines changed

apps/sim/lib/core/security/csp.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,18 @@ export function getWorkflowExecutionCSPPolicy(): string {
201201
return "default-src * 'unsafe-inline' 'unsafe-eval'; connect-src *;"
202202
}
203203

204+
/**
205+
* CSP for embeddable chat pages
206+
* Allows embedding in iframes from any origin while maintaining other security policies
207+
*/
208+
export function getChatEmbedCSPPolicy(): string {
209+
const basePolicy = buildCSPString({
210+
...buildTimeCSPDirectives,
211+
'frame-ancestors': ['*'],
212+
})
213+
return basePolicy
214+
}
215+
204216
/**
205217
* CSP for embeddable form pages
206218
* Allows embedding in iframes from any origin while maintaining other security policies

apps/sim/next.config.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { NextConfig } from 'next'
22
import { env, getEnv, isTruthy } from './lib/core/config/env'
33
import { isDev } from './lib/core/config/feature-flags'
44
import {
5+
getChatEmbedCSPPolicy,
56
getFormEmbedCSPPolicy,
67
getMainCSPPolicy,
78
getWorkflowExecutionCSPPolicy,
@@ -255,6 +256,24 @@ const nextConfig: NextConfig = {
255256
},
256257
],
257258
},
259+
// Chat pages - allow iframe embedding from any origin
260+
{
261+
source: '/chat/:path*',
262+
headers: [
263+
{
264+
key: 'X-Content-Type-Options',
265+
value: 'nosniff',
266+
},
267+
// No X-Frame-Options to allow iframe embedding
268+
{
269+
key: 'Content-Security-Policy',
270+
value: getChatEmbedCSPPolicy(),
271+
},
272+
// Permissive CORS for chat requests from embedded chats
273+
{ key: 'Cross-Origin-Embedder-Policy', value: 'unsafe-none' },
274+
{ key: 'Cross-Origin-Opener-Policy', value: 'unsafe-none' },
275+
],
276+
},
258277
// Form pages - allow iframe embedding from any origin
259278
{
260279
source: '/form/:path*',
@@ -284,10 +303,10 @@ const nextConfig: NextConfig = {
284303
],
285304
},
286305
// Apply security headers to routes not handled by middleware runtime CSP
287-
// Middleware handles: /, /workspace/*, /chat/*
288-
// Exclude form routes which have their own permissive headers
306+
// Middleware handles: /, /workspace/*
307+
// Exclude chat and form routes which have their own permissive embed headers
289308
{
290-
source: '/((?!workspace|chat$|form).*)',
309+
source: '/((?!workspace|chat|form).*)',
291310
headers: [
292311
{
293312
key: 'X-Content-Type-Options',

apps/sim/proxy.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ export async function proxy(request: NextRequest) {
155155
return response
156156
}
157157

158+
// Chat pages are publicly accessible embeds — CSP is set in next.config.ts headers
158159
if (url.pathname.startsWith('/chat/')) {
159160
return NextResponse.next()
160161
}
@@ -188,11 +189,7 @@ export async function proxy(request: NextRequest) {
188189
const response = NextResponse.next()
189190
response.headers.set('Vary', 'User-Agent')
190191

191-
if (
192-
url.pathname.startsWith('/workspace') ||
193-
url.pathname.startsWith('/chat') ||
194-
url.pathname === '/'
195-
) {
192+
if (url.pathname.startsWith('/workspace') || url.pathname === '/') {
196193
response.headers.set('Content-Security-Policy', generateRuntimeCSP())
197194
}
198195

0 commit comments

Comments
 (0)