// ============================================================================= // 0pt -- Next.js config // ============================================================================= // SECURITY HEADERS (BACKLOG P0-13 / SEC-06). // // CHANGELOG.md has claimed since 2026-08 that this repo ships // CSP/HSTS/X-Frame-Options. Until this commit that claim was FALSE on `main`: // a repo-wide grep for `content-security-policy|strict-transport-security| // x-frame-options` matched exactly one file -- CHANGELOG.md itself. There was // no `headers()`, no middleware header injection, no reverse-proxy config in // this repo. The only thing that ever set HSTS on 0pt.app was Vercel's own // platform default, which is not this repo's and disappears the moment the app // moves to Coolify/Traefik (siax.config.json `deploy.target` is already // "coolify"; .gitea/workflows/cd.yml already points at a real Coolify app id). // Setting them here, at the framework level, means they travel with the app // regardless of host -- same idiom as sax3l/siax-io's next.config.mjs. // // CARVE-OUTS, each grep-verified against THIS tree (2026-09-07), not inherited // from an older branch: // // * script-src/style-src 'unsafe-inline' -- Next's App Router injects inline // hydration/RSC-payload scripts and next-themes injects an inline // no-flash script; Tailwind + recharts emit inline style attributes. A // per-request nonce threaded through middleware + layout is the real fix // and is deliberately NOT attempted here: this commit closes "zero // headers", not "zero inline tolerance". Tracked in BACKLOG. // * 'unsafe-eval' is added ONLY in `next dev` (webpack/Turbopack eval source // maps + HMR). Production never gets it. Guarded on NODE_ENV so a // dev-only relaxation can never leak into a built image, whose runtime // stage sets NODE_ENV=production (see Dockerfile). // * form-action 'self' -- no cross-origin
exists (the only // in the tree, app/portal/integrationer/page.tsx, is onSubmit-only). // The Stripe hop in app/bestall/forskott/page.tsx is // `window.location.href = body.url`, i.e. a top-level navigation, which no // shipped CSP directive gates -- `form-action` would not have applied to // it. An earlier draft of this change carried a // `form-action https://checkout.stripe.com` carve-out on that mistaken // premise; it is dropped here rather than kept as decorative permission. // * connect-src 'self' -- every external host in the tree // (api.stripe.com, graph.microsoft.com, login.microsoftonline.com, // id-customers.siax.io, api.opensanctions.org, opendata.rdw.nl) is reached // from server-side route handlers / adapters, never from the browser. // * frame-src -- see FRAME_SRC below. This is the one directive with a live // trap in it, so it is derived from env rather than hardcoded to 'self'. // // WHAT THIS FILE CANNOT DO: it sets response headers. It does not prove they // are served. `tests/security/security-headers.test.ts` asserts this config // keeps them (blocking, runs in "CI (SIAX Cloud) / quality"), and // `node scripts/siax-doctor.mjs --live` re-checks them against a running // instance. Neither replaces the other. // ============================================================================= const IS_DEV = process.env.NODE_ENV === "development" /** * `components/embed.tsx` renders a NocoDB / Metabase view in an