Skip to content

Commit f7c7538

Browse files
waleedlatifwaleedlatif
authored andcommitted
fix(csp): runtime variable resolution for CSP
1 parent 22b2f3b commit f7c7538

File tree

3 files changed

+80
-14
lines changed

3 files changed

+80
-14
lines changed

apps/sim/lib/security/csp.ts

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { env } from '../env'
1+
import crypto from 'crypto'
2+
import { env, getEnv } from '../env'
23

34
/**
45
* Content Security Policy (CSP) configuration builder
@@ -20,7 +21,8 @@ export interface CSPDirectives {
2021
'object-src'?: string[]
2122
}
2223

23-
export const cspDirectives: CSPDirectives = {
24+
// Build-time CSP directives (for next.config.ts)
25+
export const buildTimeCSPDirectives: CSPDirectives = {
2426
'default-src': ["'self'"],
2527

2628
'script-src': [
@@ -115,10 +117,60 @@ export function buildCSPString(directives: CSPDirectives): string {
115117
}
116118

117119
/**
118-
* Get the main CSP policy string
120+
* Generate runtime CSP header with dynamic environment variables (safer approach)
121+
* This maintains compatibility with existing inline scripts while fixing Docker env var issues
122+
*/
123+
export function generateRuntimeCSP(): string {
124+
const socketUrl = getEnv('NEXT_PUBLIC_SOCKET_URL') || 'http://localhost:3002'
125+
const socketWsUrl =
126+
socketUrl.replace('http://', 'ws://').replace('https://', 'wss://') || 'ws://localhost:3002'
127+
const appUrl = getEnv('NEXT_PUBLIC_APP_URL') || ''
128+
const ollamaUrl = getEnv('OLLAMA_URL') || 'http://localhost:11434'
129+
130+
return `
131+
default-src 'self';
132+
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.google.com https://apis.google.com https://*.vercel-scripts.com https://*.vercel-insights.com https://vercel.live https://*.vercel.live https://vercel.com https://*.vercel.app https://vitals.vercel-insights.com https://b2bjsstore.s3.us-west-2.amazonaws.com;
133+
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
134+
img-src 'self' data: blob: https://*.googleusercontent.com https://*.google.com https://*.atlassian.com https://cdn.discordapp.com https://*.githubusercontent.com https://*.public.blob.vercel-storage.com;
135+
media-src 'self' blob:;
136+
font-src 'self' https://fonts.gstatic.com;
137+
connect-src 'self' ${appUrl} ${ollamaUrl} ${socketUrl} ${socketWsUrl} https://*.up.railway.app wss://*.up.railway.app https://api.browser-use.com https://api.exa.ai https://api.firecrawl.dev https://*.googleapis.com https://*.amazonaws.com https://*.s3.amazonaws.com https://*.blob.core.windows.net https://*.vercel-insights.com https://vitals.vercel-insights.com https://*.atlassian.com https://*.supabase.co https://vercel.live https://*.vercel.live https://vercel.com https://*.vercel.app wss://*.vercel.app https://pro.ip-api.com;
138+
frame-src https://drive.google.com https://docs.google.com https://*.google.com;
139+
frame-ancestors 'self';
140+
form-action 'self';
141+
base-uri 'self';
142+
object-src 'none';
143+
`
144+
.replace(/\s{2,}/g, ' ')
145+
.trim()
146+
}
147+
148+
/**
149+
* Get the main CSP policy string (build-time)
119150
*/
120151
export function getMainCSPPolicy(): string {
121-
return buildCSPString(cspDirectives)
152+
return buildCSPString(buildTimeCSPDirectives)
153+
}
154+
155+
/**
156+
* Generate a cryptographically secure nonce for CSP
157+
*/
158+
export function generateNonce(): string {
159+
return Buffer.from(crypto.randomUUID()).toString('base64')
160+
}
161+
162+
/**
163+
* Get the current request's nonce from headers (for use in app components)
164+
*/
165+
export function getNonce(): string | null {
166+
try {
167+
// This requires dynamic imports to avoid edge runtime issues
168+
const { headers } = require('next/headers')
169+
const headersList = headers()
170+
return headersList.get('x-nonce')
171+
} catch {
172+
return null
173+
}
122174
}
123175

124176
/**
@@ -129,22 +181,24 @@ export function getWorkflowExecutionCSPPolicy(): string {
129181
}
130182

131183
/**
132-
* Add a source to a specific directive
184+
* Add a source to a specific directive (modifies build-time directives)
133185
*/
134186
export function addCSPSource(directive: keyof CSPDirectives, source: string): void {
135-
if (!cspDirectives[directive]) {
136-
cspDirectives[directive] = []
187+
if (!buildTimeCSPDirectives[directive]) {
188+
buildTimeCSPDirectives[directive] = []
137189
}
138-
if (!cspDirectives[directive]!.includes(source)) {
139-
cspDirectives[directive]!.push(source)
190+
if (!buildTimeCSPDirectives[directive]!.includes(source)) {
191+
buildTimeCSPDirectives[directive]!.push(source)
140192
}
141193
}
142194

143195
/**
144-
* Remove a source from a specific directive
196+
* Remove a source from a specific directive (modifies build-time directives)
145197
*/
146198
export function removeCSPSource(directive: keyof CSPDirectives, source: string): void {
147-
if (cspDirectives[directive]) {
148-
cspDirectives[directive] = cspDirectives[directive]!.filter((s: string) => s !== source)
199+
if (buildTimeCSPDirectives[directive]) {
200+
buildTimeCSPDirectives[directive] = buildTimeCSPDirectives[directive]!.filter(
201+
(s: string) => s !== source
202+
)
149203
}
150204
}

apps/sim/middleware.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { getSessionCookie } from 'better-auth/cookies'
22
import { type NextRequest, NextResponse } from 'next/server'
33
import { isDev } from './lib/environment'
44
import { createLogger } from './lib/logs/console/logger'
5+
import { generateRuntimeCSP } from './lib/security/csp'
56
import { getBaseDomain } from './lib/urls/utils'
67

78
const logger = createLogger('Middleware')
@@ -159,6 +160,16 @@ export async function middleware(request: NextRequest) {
159160

160161
const response = NextResponse.next()
161162
response.headers.set('Vary', 'User-Agent')
163+
164+
// Generate runtime CSP for main application routes that need dynamic environment variables
165+
if (
166+
url.pathname.startsWith('/workspace') ||
167+
url.pathname.startsWith('/chat') ||
168+
url.pathname === '/'
169+
) {
170+
response.headers.set('Content-Security-Policy', generateRuntimeCSP())
171+
}
172+
162173
return response
163174
}
164175

apps/sim/next.config.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,10 @@ const nextConfig: NextConfig = {
133133
},
134134
],
135135
},
136-
// Apply security headers to all routes
136+
// Apply security headers to routes not handled by middleware runtime CSP
137+
// Middleware handles: /, /workspace/*, /chat/*
137138
{
138-
source: '/:path*',
139+
source: '/((?!workspace|chat$).*)',
139140
headers: [
140141
{
141142
key: 'X-Content-Type-Options',

0 commit comments

Comments
 (0)