Files
craftvia/next.config.ts
T
msolarczekandClaude Opus 5 c8e6f30a27
CI / build-and-check (push) Canceled after 0s
CI / audit (push) Canceled after 0s
CI / sbom (push) Canceled after 0s
Basis: Certvia dev@a48c5fb als Fundament für Craftvia
Unveränderter Stand von certvia/dev (a48c5fb) plus Craftvia-Spezifikation
und Brandbook unter docs/craftvia/. ISMS-Module werden im Folgecommit entfernt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 11:05:39 +02:00

62 lines
2.4 KiB
TypeScript

import type { NextConfig } from "next";
import createNextIntlPlugin from "next-intl/plugin";
// Im Dev-Betrieb braucht Turbopack/HMR 'unsafe-eval' und WebSocket-Verbindungen
// zum Dev-Server. Diese Lockerungen gelten NUR in der Entwicklung, nie im Build.
const isDev = process.env.NODE_ENV !== "production";
// Content-Security-Policy (F-07) — zweite Verteidigungslinie hinter dem
// HTML-Sanitizing (F-03).
//
// Hinweis Skripte: 'unsafe-inline' ist ein bewusster Zwischenstand. Next.js
// liefert seinen Hydration-Bootstrap als Inline-Skript aus; eine Nonce-basierte
// CSP (Nonce in proxy.ts erzeugen und an <script>/Next durchreichen) ist ein
// eigenes Folgepaket. Bis dahin bleibt 'unsafe-inline' für Skripte bestehen.
const csp = [
"default-src 'self'",
// Skripte: 'unsafe-inline' als Zwischenstand (s.o.); 'unsafe-eval' nur im Dev
// (Turbopack/HMR wertet zur Laufzeit aus, im Produktionsbuild nicht nötig).
`script-src 'self' 'unsafe-inline'${isDev ? " 'unsafe-eval'" : ""}`,
// Next.js sowie @xyflow/react und Tailwind v4 setzen Inline-Styles.
"style-src 'self' 'unsafe-inline'",
// data: für den MFA-QR-Code (qrcode → data:-URI); blob: für Graph-Bildexport
// (html-to-image) und ähnliche clientseitig erzeugte Bilder.
"img-src 'self' data: blob:",
"font-src 'self' data:",
// Im Dev zusätzlich der HMR-WebSocket des Dev-Servers.
`connect-src 'self'${isDev ? " ws: wss:" : ""}`,
// html-to-image kann Worker aus Blobs erzeugen.
"worker-src 'self' blob:",
"frame-ancestors 'none'",
"form-action 'self'",
"base-uri 'self'",
"object-src 'none'",
].join("; ");
const securityHeaders = [
{ key: "Content-Security-Policy", value: csp },
// HSTS bewusst zusätzlich in der App (neben dem Coolify-/Traefik-Proxy).
{ key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "X-Frame-Options", value: "DENY" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=(), payment=()" },
];
const nextConfig: NextConfig = {
// Standalone-Output für den Docker-Multi-Stage-Build (siehe Dockerfile)
output: "standalone",
async headers() {
return [
{
source: "/:path*",
headers: securityHeaders,
},
];
},
};
const withNextIntl = createNextIntlPlugin();
export default withNextIntl(nextConfig);