mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-21 13:00:04 +08:00
feat(csp): allow chat UI to be embedded in iframes (#3643)
* 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> * 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 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
75a3e2c3a8
commit
c9f082da1a
@@ -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()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+22
-3
@@ -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',
|
||||
|
||||
+2
-5
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user