scripts/console-security-policy.ts
Rama main · réplica de lectura del repositorio original.
/**
* Single source of truth for the Capgo console Content-Security-Policy.
*
* Sync into `public/_headers` with:
* bun run security:sync-headers
*/
import configs from '../configs.json'
export interface ConsoleCspOptions {
/** Local Vite dev server — allow http/ws to localhost. */
dev?: boolean
}
function joinSources(sources: string[]) {
return [...new Set(sources)].join(' ')
}
function addConnectOrigin(sources: Set<string>, raw: string) {
if (!raw)
return
if (raw.includes('://')) {
try {
const url = new URL(raw)
sources.add(url.origin)
if (url.protocol === 'https:')
sources.add(`wss://${url.host}`)
else if (url.protocol === 'http:' && (url.hostname === 'localhost' || url.hostname === '127.0.0.1'))
sources.add(`ws://${url.host}`)
}
catch {
// Ignore invalid configured URLs.
}
return
}
const host = raw.split('/')[0]
if (host.includes('localhost') || host.startsWith('127.')) {
sources.add(`http://${host}`)
sources.add(`ws://${host}`)
}
else {
sources.add(`https://${host}`)
}
}
function getDeployTimeConnectOverrides(): string[] {
const overrides: string[] = []
const apiDomain = process.env.API_DOMAIN
if (typeof apiDomain === 'string' && apiDomain)
overrides.push(apiDomain)
return overrides
}
function getConfiguredConnectSources(dev: boolean): string[] {
const sources = new Set<string>()
const envKeys = dev
? (['prod', 'preprod', 'development', 'local'] as const)
: (['prod'] as const)
for (const env of envKeys) {
const apiDomain = configs.api_domain?.[env]
if (typeof apiDomain === 'string')
addConnectOrigin(sources, apiDomain)
}
for (const raw of getDeployTimeConnectOverrides())
addConnectOrigin(sources, raw)
return [...sources]
}
const PREVIEW_CONNECT_SOURCES = [
'https://*.preview.capgo.app',
'https://*.preview.preprod.capgo.app',
'https://*.preview.dev.capgo.app',
'https://*.preview.development.capgo.app',
]
export function buildConsoleContentSecurityPolicy(options: ConsoleCspOptions = {}): string {
const dev = options.dev === true
const scriptSrc = joinSources([
'\'self\'',
'https://challenges.cloudflare.com', // Cloudflare Turnstile
'https://static.cloudflareinsights.com', // Cloudflare Web Analytics (if enabled)
'https://psthg.capgo.app', // first-party PostHog reverse proxy
'https://js.stripe.com', // Stripe.js for embedded Checkout / Payment Element
'https://*.js.stripe.com',
])
const styleSrc = joinSources([
'\'self\'',
'\'unsafe-inline\'', // Vue / Tailwind runtime styles
])
const fontSrc = joinSources([
'\'self\'',
'data:',
])
const imgSrc = joinSources([
'\'self\'',
'data:',
'blob:',
'https:', // org logos, avatars, store icons, signed storage URLs
])
const mediaSrc = joinSources([
'\'self\'',
'data:',
'blob:',
'https:',
])
const connectSrc = joinSources([
'\'self\'',
'blob:',
'https://psthg.capgo.app',
'https://eu.posthog.com',
'https://eu.i.posthog.com',
'https://challenges.cloudflare.com',
'https://api.github.com',
'https://registry.npmjs.org',
'https://api.stripe.com', // Stripe.js API calls
'https://checkout.stripe.com', // embedded Checkout
'https://merchant-ui-api.stripe.com',
'https://hooks.stripe.com',
'https://auth.openai.com', // assistant ChatGPT sign-in token exchange
'https://api.openai.com', // assistant model calls
...PREVIEW_CONNECT_SOURCES,
...getConfiguredConnectSources(dev),
...(dev
? [
'http://localhost:*',
'http://127.0.0.1:*',
'ws://localhost:*',
'ws://127.0.0.1:*',
]
: []),
])
const frameSrc = joinSources([
'\'self\'',
'https://challenges.cloudflare.com', // Turnstile widget
'https://checkout.stripe.com',
'https://billing.stripe.com',
'https://js.stripe.com',
'https://*.js.stripe.com',
'https://hooks.stripe.com', // 3DS challenge frames
...PREVIEW_CONNECT_SOURCES,
])
const directives = [
'default-src \'self\'',
'base-uri \'self\'',
'object-src \'none\'',
'frame-ancestors \'none\'',
'form-action \'self\' https://checkout.stripe.com https://billing.stripe.com',
`script-src ${scriptSrc}`,
`style-src ${styleSrc}`,
`img-src ${imgSrc}`,
`font-src ${fontSrc}`,
`connect-src ${connectSrc}`,
`media-src ${mediaSrc}`,
`frame-src ${frameSrc}`,
'worker-src \'self\' blob:',
'manifest-src \'self\'',
]
if (!dev)
directives.push('upgrade-insecure-requests')
return directives.join('; ')
}
export const CONSOLE_CONTENT_SECURITY_POLICY = buildConsoleContentSecurityPolicy()