// ── Content-Security-Policy ────────────────────────────────────────────────
//
// Two policies ship at once, on two different headers:
//
// Content-Security-Policy → `enforced` (today's policy, unchanged)
// Content-Security-Policy-Report-Only → `reportOnly` (the target, + a report sink)
//
// Report-only first, one release of observation, then the two collapse into one
// enforced policy (docs/website/API_V2_PLAN.md § Phase 1). Shipping the tightened
// policy straight to `Content-Security-Policy` would mean discovering any legitimate
// use we forgot as a broken page in production; shipping it *alongside* the current
// one means a violation report instead, with the live policy still protecting users
// the whole time.
//
// Notes on each non-'self' allowance in the base policy:
// • style-src 'unsafe-inline' — React renders pervasive inline `style={{…}}`
// attributes, and CSP style *attributes* cannot be nonce'd; this is required.
// It permits inline styling, not script execution. Also whitelists the Google
// Fonts stylesheet host.
// • font-src — Google Fonts (Cinzel) serves the font files from gstatic.
// • img-src https:/data: — uploaded images are same-origin, but wiki/news bodies
// (sanitizeHtml allows over http/https) and BRAND_* logo/hero/favicon may
// point at external https images. http images are blocked by mixed-content on
// the https site anyway.
// • connect-src 'self' — the REST API and SSE streams are same-origin. This is the
// exfiltration channel; do not widen it unless the API genuinely becomes
// cross-origin (which would also reopen the auth-merge question — see the plan).
// • script-src 'self' with no 'unsafe-inline'/'unsafe-eval' is the primary defense.
// Vite is configured with `modulePreload: { polyfill: false }` (client/vite.config.js)
// precisely so the build emits no inline bootstrap script for this to trip on.
// • upgrade-insecure-requests is intentionally dropped: TLS is terminated at the
// proxy, there are no mixed-content subresources to upgrade, and leaving it on
// breaks a local `npm start` served over plain http.
//
// The interactive API docs at /api/docs get their own looser policy (swagger-ui
// injects an inline bootstrap script); that carve-out lives in app.js and stays
// scoped to the one route.
// Where violation reports are POSTed, and the Reporting-API group name that points
// at it. Same-origin on purpose — reports describe attacks against this site and
// must not be shipped to a third party.
const REPORT_PATH = '/api/csp-report'
const REPORT_GROUP = 'csp-endpoint'
// The policy in force today. Behaviourally unchanged by this phase — it is the safety
// net while the tightened twin is only being observed.
const enforced = {
'default-src': ["'self'"],
'script-src': ["'self'"],
'style-src': ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com'],
'font-src': ["'self'", 'https://fonts.gstatic.com'],
'img-src': ["'self'", 'data:', 'https:'],
'connect-src': ["'self'"],
'frame-ancestors': ["'self'"],
'object-src': ["'none'"],
'base-uri': ["'self'"],
// Blocks an injected `