L4 aus dem TODO-Block Sicherheitsdurchgang. Bewusst vorsichtig: die Policy laeuft zunaechst nur als "Content-Security-Policy-Report-Only" und blockiert nichts, meldet aber ueber den neuen Endpunkt POST /api/csp-report jeden Verstoss (console.error/PM2). Whitelist aus dem tatsaechlichen Code abgeleitet, nicht geraten: - fonts.googleapis.com/fonts.gstatic.com: FontPicker.tsx/WeeklyView.tsx laden Nutzer-Schriftarten zur Laufzeit per <link> nach - 'unsafe-inline' fuer script-src/style-src: Next.js' App-Router-Hydration und der eine dangerouslySetInnerHTML-Block in WeeklyView.tsx (View-Transition-Styles) brauchen das ohne Nonce-Infrastruktur (eigener, groesserer Umbau) - Kein <iframe>, keine Drittanbieter-Bilder, keine clientseitigen fetch()-Aufrufe zu fremden Domains gefunden. FontAwesome laeuft als React-Komponente im eigenen Bundle. OAuth-Redirects sind Top-Level-Navigation, von CSP nicht betroffen. src/middleware.ts: /api/csp-report von der Login-Pflicht ausgenommen -- der Browser schickt Reports automatisch, auch von der nicht angemeldeten Login-Seite. Scharf schalten (Report-Only aus dem Header-Namen entfernen) erst, nachdem ein paar Tage Reports durchgesehen wurden -- die OAuth-Flows (Google/Microsoft/Apple/Notion) liessen sich hier nicht interaktiv durchklicken. Verifiziert: Typecheck, Lint, 85 Unit-Tests, Produktions-Build, Header und Report-Endpunkt manuell im gebauten Standalone-Server geprueft. v1.125.0 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
105 lines
4.9 KiB
JavaScript
105 lines
4.9 KiB
JavaScript
/** @type {import('next').NextConfig} */
|
|
const { version } = require('./package.json');
|
|
|
|
const nextConfig = {
|
|
output: 'standalone',
|
|
// Next.js sucht per Lockfile-Suche nach oben selbst einen "Workspace-Root" fuer die
|
|
// File-Tracing des Standalone-Outputs. Findet es dort ein weiteres Lockfile (auf dem
|
|
// Produktionshost liegt in /root eine verwaiste, leere package-lock.json), haelt es
|
|
// /root faelschlich fuer die Wurzel und verschachtelt den Output um einen Ordner tiefer:
|
|
// .next-build/standalone/My-Weekly-ToDo-List/server.js statt .next-build/standalone/
|
|
// server.js. scripts/update-app.sh erwartet Letzteres fest und der Smoke-Test schlaegt
|
|
// sonst fehl ("Cannot find module '.../standalone/server.js'"), live aufgetreten
|
|
// 2026-09-12 beim ersten Deploy nach next@15. __dirname verankert die Wurzel explizit,
|
|
// unabhaengig davon, was sonst noch im Dateisystem oberhalb des Projekts herumliegt.
|
|
outputFileTracingRoot: __dirname,
|
|
// Kein "X-Powered-By: Next.js" -- unnoetige Versions-/Framework-Preisgabe an Scanner.
|
|
poweredByHeader: false,
|
|
// Ziel des Builds. update-app.sh baut nach .next-build, damit das laufende .next
|
|
// waehrend des Builds unberuehrt bleibt, und schaltet erst nach erfolgreichem
|
|
// Smoke-Test um. Ohne das zerstoert ein fehlgeschlagener Build die laufende App.
|
|
distDir: process.env.NEXT_DIST_DIR || '.next',
|
|
eslint: {
|
|
// Aus: Der Build meldet ESLint-Fehler. Warnungen blockieren ihn nicht, nur Errors.
|
|
//
|
|
// Kurzzeitig stand hier wieder true, weil ich ESLint fuer den OOM-Absturz des Builds
|
|
// auf dem Produktionscontainer verantwortlich hielt. Eine Messung widerlegte das:
|
|
// Spitzenspeicher mit ESLint 2.590 MB, ohne 2.595 MB -- kein Unterschied. Die Ursache
|
|
// war Nodes Standard-Heap-Limit von rund 1,5 GB; dafuer setzt scripts/update-app.sh
|
|
// jetzt NODE_OPTIONS.
|
|
ignoreDuringBuilds: false,
|
|
},
|
|
typescript: {
|
|
// Aus: Der Build bricht bei Typfehlern ab. Bis v1.118.1 stand hier true — dadurch
|
|
// liefen mehrfach fehlende Importe durch, die zur Laufzeit ReferenceError geworfen
|
|
// haetten, waehrend der Build "erfolgreich" meldete.
|
|
ignoreBuildErrors: false,
|
|
},
|
|
optimizeFonts: false,
|
|
env: {
|
|
NEXT_PUBLIC_APP_VERSION: version,
|
|
},
|
|
experimental: {
|
|
serverComponentsExternalPackages: ['ical.js', 'tsdav'],
|
|
},
|
|
// Standard-Sicherheitsheader, die von Scannern (securityheaders.com, Mozilla Observatory,
|
|
// OWASP ZAP Baseline) sonst als fehlend markiert werden.
|
|
//
|
|
// Content-Security-Policy laeuft bewusst nur als Report-Only: sie blockiert nichts, meldet
|
|
// aber ueber /api/csp-report, was eine scharfe Policy blockieren wuerde. Die Whitelist ist
|
|
// aus dem tatsaechlichen Code abgeleitet (12.09.2026):
|
|
// - script-src/style-src 'unsafe-inline': Next.js' App-Router-Hydration-Skripte und der
|
|
// eine dangerouslySetInnerHTML-Block in WeeklyView.tsx (View-Transition-Styles) brauchen
|
|
// das ohne Nonce-Infrastruktur (eigener, groesserer Umbau via Middleware).
|
|
// - style-src/font-src fonts.googleapis.com/fonts.gstatic.com: FontPicker.tsx und
|
|
// WeeklyView.tsx laden Nutzer-Schriftarten zur Laufzeit per <link>-Tag nach.
|
|
// - Keine weiteren externen Domains gefunden: kein <iframe>, keine Bilder von
|
|
// Drittanbietern (Profilbilder werden nirgends gerendert), keine Client-seitigen
|
|
// fetch()-Aufrufe zu Fremd-Domains (Notion-OAuth laeuft serverseitig in einer API-Route).
|
|
// Erst nach ein paar Tagen Reports pruefen -- vor allem die OAuth-Login-Flows (Google/
|
|
// Microsoft/Apple/Notion) liessen sich hier nicht interaktiv durchklicken -- scharf schalten:
|
|
// "-Report-Only" aus dem Header-Namen entfernen und report-uri optional behalten.
|
|
async headers() {
|
|
const csp = [
|
|
"default-src 'self'",
|
|
"script-src 'self' 'unsafe-inline'",
|
|
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
|
|
"font-src 'self' https://fonts.gstatic.com data:",
|
|
"img-src 'self' data: blob:",
|
|
"connect-src 'self'",
|
|
"frame-src 'none'",
|
|
"frame-ancestors 'none'",
|
|
"object-src 'none'",
|
|
"base-uri 'self'",
|
|
"form-action 'self'",
|
|
"manifest-src 'self'",
|
|
"worker-src 'self'",
|
|
"report-uri /api/csp-report",
|
|
].join('; ');
|
|
|
|
return [
|
|
{
|
|
source: '/:path*',
|
|
headers: [
|
|
{ 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=()' },
|
|
{ key: 'Content-Security-Policy-Report-Only', value: csp },
|
|
],
|
|
},
|
|
];
|
|
},
|
|
webpack: (config, { isServer }) => {
|
|
// Ignore .md files in node_modules (fixes apple-icloud README.md parse error)
|
|
config.module.rules.push({
|
|
test: /\.md$/,
|
|
include: /node_modules/,
|
|
type: 'asset/source',
|
|
});
|
|
return config;
|
|
},
|
|
};
|
|
|
|
module.exports = nextConfig;
|