Ctrl K
ComunidadGuías…

scripts/console-security-policy.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/**
 * 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()