chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,285 @@
|
||||
import { env, getEnv } from '../config/env'
|
||||
import { isDev, isHosted, isReactGrabEnabled } from '../config/env-flags'
|
||||
|
||||
/**
|
||||
* Content Security Policy (CSP) configuration builder
|
||||
*
|
||||
* NOTE: This file is loaded by next.config.ts at build time, before @/ path
|
||||
* aliases are resolved. Do NOT import from ../utils/urls (which uses @/ imports).
|
||||
* Keep all URL constants local to this file.
|
||||
*/
|
||||
|
||||
const DEFAULT_SOCKET_URL = 'http://localhost:3002'
|
||||
const DEFAULT_OLLAMA_URL = 'http://localhost:11434'
|
||||
|
||||
function toWebSocketUrl(httpUrl: string): string {
|
||||
return httpUrl.replace('http://', 'ws://').replace('https://', 'wss://')
|
||||
}
|
||||
|
||||
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[]
|
||||
'style-src'?: string[]
|
||||
'img-src'?: string[]
|
||||
'media-src'?: string[]
|
||||
'font-src'?: string[]
|
||||
'connect-src'?: string[]
|
||||
'worker-src'?: string[]
|
||||
'frame-src'?: string[]
|
||||
'frame-ancestors'?: string[]
|
||||
'form-action'?: string[]
|
||||
'base-uri'?: string[]
|
||||
'object-src'?: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Static CSP sources shared between build-time and runtime.
|
||||
* Add new domains here — both paths pick them up automatically.
|
||||
*/
|
||||
const STATIC_SCRIPT_SRC = [
|
||||
"'self'",
|
||||
"'unsafe-inline'",
|
||||
...(isDev ? ["'unsafe-eval'"] : []),
|
||||
'https://*.google.com',
|
||||
'https://apis.google.com',
|
||||
'https://challenges.cloudflare.com',
|
||||
// Cal.com booking embed (landing /demo) — embed.js is served from app.cal.com
|
||||
'https://app.cal.com',
|
||||
...(isReactGrabEnabled ? ['https://unpkg.com'] : []),
|
||||
...(isHosted
|
||||
? [
|
||||
'https://www.googletagmanager.com',
|
||||
'https://www.google-analytics.com',
|
||||
'https://analytics.ahrefs.com',
|
||||
// HubSpot tracking (landing pages) — loader plus the
|
||||
// analytics/form-tracking/banner scripts it injects as <script> tags
|
||||
'https://*.hs-scripts.com',
|
||||
'https://*.hs-analytics.net',
|
||||
'https://*.hscollectedforms.net',
|
||||
'https://*.hs-banner.com',
|
||||
]
|
||||
: []),
|
||||
] as const
|
||||
|
||||
const STATIC_IMG_SRC = ["'self'", 'data:', 'blob:', 'https:'] as const
|
||||
|
||||
const STATIC_CONNECT_SRC = [
|
||||
"'self'",
|
||||
'https://api.browser-use.com',
|
||||
'https://api.elevenlabs.io',
|
||||
'wss://api.elevenlabs.io',
|
||||
'https://api.exa.ai',
|
||||
'https://api.firecrawl.dev',
|
||||
'https://*.googleapis.com',
|
||||
'https://*.amazonaws.com',
|
||||
'https://*.s3.amazonaws.com',
|
||||
'https://*.blob.core.windows.net',
|
||||
'https://*.atlassian.com',
|
||||
'https://*.supabase.co',
|
||||
'https://api.github.com',
|
||||
'https://github.com/*',
|
||||
'https://challenges.cloudflare.com',
|
||||
// Cal.com booking embed (landing /demo) — embed XHR/availability calls
|
||||
'https://app.cal.com',
|
||||
'https://cal.com',
|
||||
...(isReactGrabEnabled ? ['https://www.react-grab.com'] : []),
|
||||
...(isDev ? ['ws://localhost:4722'] : []),
|
||||
...(isHosted
|
||||
? [
|
||||
'https://www.googletagmanager.com',
|
||||
'https://*.google-analytics.com',
|
||||
'https://*.analytics.google.com',
|
||||
'https://analytics.google.com',
|
||||
'https://www.google.com',
|
||||
'https://analytics.ahrefs.com',
|
||||
'https://*.g.doubleclick.net',
|
||||
// HubSpot tracking — form-tracking API (hscollectedforms.js).
|
||||
// The visitor beacon itself is an image pixel (img-src, already
|
||||
// permitted below), not a connect-src request.
|
||||
'https://*.hscollectedforms.net',
|
||||
]
|
||||
: []),
|
||||
] as const
|
||||
|
||||
const STATIC_FRAME_SRC = [
|
||||
"'self'",
|
||||
'blob:',
|
||||
'https://challenges.cloudflare.com',
|
||||
// Cal.com booking embed (landing /demo) — the booking iframe
|
||||
'https://app.cal.com',
|
||||
'https://cal.com',
|
||||
'https://drive.google.com',
|
||||
'https://docs.google.com',
|
||||
'https://*.google.com',
|
||||
'https://www.youtube.com',
|
||||
'https://player.vimeo.com',
|
||||
'https://www.dailymotion.com',
|
||||
'https://player.twitch.tv',
|
||||
'https://clips.twitch.tv',
|
||||
'https://streamable.com',
|
||||
'https://fast.wistia.net',
|
||||
'https://www.tiktok.com',
|
||||
'https://w.soundcloud.com',
|
||||
'https://open.spotify.com',
|
||||
'https://embed.music.apple.com',
|
||||
'https://www.loom.com',
|
||||
'https://www.facebook.com',
|
||||
'https://www.instagram.com',
|
||||
'https://platform.twitter.com',
|
||||
'https://rumble.com',
|
||||
'https://play.vidyard.com',
|
||||
'https://iframe.cloudflarestream.com',
|
||||
'https://www.mixcloud.com',
|
||||
'https://tenor.com',
|
||||
'https://giphy.com',
|
||||
...(isHosted ? ['https://www.googletagmanager.com'] : []),
|
||||
] as const
|
||||
|
||||
// Build-time CSP directives (for next.config.ts)
|
||||
export const buildTimeCSPDirectives: CSPDirectives = {
|
||||
'default-src': ["'self'"],
|
||||
'script-src': [...STATIC_SCRIPT_SRC],
|
||||
'style-src': ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com'],
|
||||
|
||||
'img-src': [...STATIC_IMG_SRC],
|
||||
|
||||
'media-src': ["'self'", 'blob:'],
|
||||
'worker-src': ["'self'", 'blob:'],
|
||||
'font-src': ["'self'", 'https://fonts.gstatic.com'],
|
||||
|
||||
'connect-src': [
|
||||
...STATIC_CONNECT_SRC,
|
||||
env.NEXT_PUBLIC_APP_URL || '',
|
||||
...(env.OLLAMA_URL ? [env.OLLAMA_URL] : isDev ? [DEFAULT_OLLAMA_URL] : []),
|
||||
...(env.NEXT_PUBLIC_SOCKET_URL
|
||||
? [env.NEXT_PUBLIC_SOCKET_URL, toWebSocketUrl(env.NEXT_PUBLIC_SOCKET_URL)]
|
||||
: isDev
|
||||
? [DEFAULT_SOCKET_URL, toWebSocketUrl(DEFAULT_SOCKET_URL)]
|
||||
: []),
|
||||
...getHostnameFromUrl(env.NEXT_PUBLIC_BRAND_LOGO_URL),
|
||||
...getHostnameFromUrl(env.NEXT_PUBLIC_PRIVACY_URL),
|
||||
...getHostnameFromUrl(env.NEXT_PUBLIC_TERMS_URL),
|
||||
],
|
||||
|
||||
'frame-src': [...STATIC_FRAME_SRC],
|
||||
'frame-ancestors': ["'self'"],
|
||||
'form-action': ["'self'"],
|
||||
'base-uri': ["'self'"],
|
||||
'object-src': ["'none'"],
|
||||
}
|
||||
|
||||
/**
|
||||
* Build CSP string from directives object
|
||||
*/
|
||||
export function buildCSPString(directives: CSPDirectives): string {
|
||||
return Object.entries(directives)
|
||||
.map(([directive, sources]) => {
|
||||
if (!sources || sources.length === 0) return ''
|
||||
const validSources = sources.filter((source: string) => source && source.trim() !== '')
|
||||
if (validSources.length === 0) return ''
|
||||
return `${directive} ${validSources.join(' ')}`
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join('; ')
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate runtime CSP header with dynamic environment variables.
|
||||
* Composes from the same STATIC_* constants as buildTimeCSPDirectives,
|
||||
* but resolves env vars at request time via getEnv() to fix Docker
|
||||
* deployments where build-time values may be stale placeholders.
|
||||
*/
|
||||
export function generateRuntimeCSP(): string {
|
||||
const appUrl = getEnv('NEXT_PUBLIC_APP_URL') || ''
|
||||
|
||||
const socketUrl = getEnv('NEXT_PUBLIC_SOCKET_URL') || (isDev ? DEFAULT_SOCKET_URL : '')
|
||||
const socketWsUrl = socketUrl ? toWebSocketUrl(socketUrl) : ''
|
||||
const ollamaUrl = getEnv('OLLAMA_URL') || (isDev ? DEFAULT_OLLAMA_URL : '')
|
||||
|
||||
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 runtimeDirectives: CSPDirectives = {
|
||||
...buildTimeCSPDirectives,
|
||||
|
||||
'img-src': [...STATIC_IMG_SRC],
|
||||
|
||||
'connect-src': [
|
||||
...STATIC_CONNECT_SRC,
|
||||
appUrl,
|
||||
ollamaUrl,
|
||||
socketUrl,
|
||||
socketWsUrl,
|
||||
...brandLogoDomains,
|
||||
...privacyDomains,
|
||||
...termsDomains,
|
||||
],
|
||||
}
|
||||
|
||||
return buildCSPString(runtimeDirectives)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the main CSP policy string (build-time)
|
||||
*/
|
||||
export function getMainCSPPolicy(): string {
|
||||
return buildCSPString(buildTimeCSPDirectives)
|
||||
}
|
||||
|
||||
/**
|
||||
* Permissive CSP for workflow execution endpoints
|
||||
*/
|
||||
export function getWorkflowExecutionCSPPolicy(): string {
|
||||
return "default-src * 'unsafe-inline' 'unsafe-eval'; connect-src *;"
|
||||
}
|
||||
|
||||
/**
|
||||
* CSP for embeddable chat pages.
|
||||
* Extends the shared embed policy with Microsoft Office.js sources so the
|
||||
* chat page can serve as an Office (Excel/Word/Outlook) add-in surface
|
||||
* when loaded with `?embed=office`.
|
||||
*/
|
||||
export function getChatEmbedCSPPolicy(): string {
|
||||
return buildCSPString({
|
||||
...buildTimeCSPDirectives,
|
||||
'script-src': [...STATIC_SCRIPT_SRC, 'https://appsforoffice.microsoft.com'],
|
||||
'connect-src': [
|
||||
...(buildTimeCSPDirectives['connect-src'] ?? []),
|
||||
'https://appsforoffice.microsoft.com',
|
||||
],
|
||||
'frame-ancestors': ['*'],
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a source to a specific directive (modifies build-time directives)
|
||||
*/
|
||||
export function addCSPSource(directive: keyof CSPDirectives, source: string): void {
|
||||
if (!buildTimeCSPDirectives[directive]) {
|
||||
buildTimeCSPDirectives[directive] = []
|
||||
}
|
||||
if (!buildTimeCSPDirectives[directive]!.includes(source)) {
|
||||
buildTimeCSPDirectives[directive]!.push(source)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a source from a specific directive (modifies build-time directives)
|
||||
*/
|
||||
export function removeCSPSource(directive: keyof CSPDirectives, source: string): void {
|
||||
if (buildTimeCSPDirectives[directive]) {
|
||||
buildTimeCSPDirectives[directive] = buildTimeCSPDirectives[directive]!.filter(
|
||||
(s: string) => s !== source
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user