Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 25 additions & 11 deletions apps/sim/lib/security/csp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@ import { env, getEnv } from '../env'

/**
* Content Security Policy (CSP) configuration builder
* This creates a more maintainable and readable CSP configuration
*/

function getHostnameFromUrl(url: string | undefined): string[] {
if (!url) return []
try {
return [`https://${new URL(url).hostname}`]
} catch {
return []
}
}

export interface CSPDirectives {
'default-src'?: string[]
'script-src'?: string[]
Expand Down Expand Up @@ -43,7 +51,6 @@ export const buildTimeCSPDirectives: CSPDirectives = {
'style-src': ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com'],

'img-src': [
'https://agentics.epiqglobal.com',
"'self'",
'data:',
'blob:',
Expand All @@ -66,6 +73,7 @@ export const buildTimeCSPDirectives: CSPDirectives = {
: []),
'https://*.amazonaws.com',
'https://*.blob.core.windows.net',
...getHostnameFromUrl(env.NEXT_PUBLIC_BRAND_LOGO_URL),
],

'media-src': ["'self'", 'blob:'],
Expand Down Expand Up @@ -98,14 +106,12 @@ export const buildTimeCSPDirectives: CSPDirectives = {
'https://*.vercel.app',
'wss://*.vercel.app',
'https://pro.ip-api.com',
...getHostnameFromUrl(env.NEXT_PUBLIC_BRAND_LOGO_URL),
...getHostnameFromUrl(env.NEXT_PUBLIC_PRIVACY_URL),
...getHostnameFromUrl(env.NEXT_PUBLIC_TERMS_URL),
],

// Google Picker and Drive integration
'frame-src': [
'https://drive.google.com',
'https://docs.google.com', // Required for Google Picker
'https://*.google.com',
],
'frame-src': ['https://drive.google.com', 'https://docs.google.com', 'https://*.google.com'],

'frame-ancestors': ["'self'"],
'form-action': ["'self'"],
Expand All @@ -120,7 +126,6 @@ export function buildCSPString(directives: CSPDirectives): string {
return Object.entries(directives)
.map(([directive, sources]) => {
if (!sources || sources.length === 0) return ''
// Filter out empty strings
const validSources = sources.filter((source: string) => source && source.trim() !== '')
if (validSources.length === 0) return ''
return `${directive} ${validSources.join(' ')}`
Expand All @@ -140,14 +145,23 @@ export function generateRuntimeCSP(): string {
const appUrl = getEnv('NEXT_PUBLIC_APP_URL') || ''
const ollamaUrl = getEnv('OLLAMA_URL') || 'http://localhost:11434'

const brandLogoDomains = getHostnameFromUrl(getEnv('NEXT_PUBLIC_BRAND_LOGO_URL'))
const privacyDomains = getHostnameFromUrl(getEnv('NEXT_PUBLIC_PRIVACY_URL'))
const termsDomains = getHostnameFromUrl(getEnv('NEXT_PUBLIC_TERMS_URL'))

const allDynamicDomains = [...brandLogoDomains, ...privacyDomains, ...termsDomains]
const uniqueDynamicDomains = Array.from(new Set(allDynamicDomains))
const dynamicDomainsStr = uniqueDynamicDomains.join(' ')
const brandLogoDomain = brandLogoDomains[0] || ''

return `
default-src 'self';
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;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
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;
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 ${brandLogoDomain};
media-src 'self' blob:;
font-src 'self' https://fonts.gstatic.com;
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;
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 ${dynamicDomainsStr};
frame-src https://drive.google.com https://docs.google.com https://*.google.com;
frame-ancestors 'self';
form-action 'self';
Expand Down