fix: Login komplett kaputt seit dem Security-Header-Commit - CSP blockierte jede API-Anfrage
Backend Image / build-and-push (push) Successful in 2m16s
Frontend Image / build-and-push (push) Successful in 2m1s

Root Cause, zweifach:

1. proxy.ts lag im Projekt-Root statt unter src/ (wo app/ tatsaechlich
   liegt). Next.js hat die Datei dadurch nie erkannt - ueberprueft via
   Build-Output ("Proxy (Middleware)" fehlte komplett) und via
   routes-manifest.json. Das betraf nicht nur die neuen
   Security-Header, sondern auch die schon laenger bestehende
   Login-Redirect-Logik, die dadurch vermutlich nie serverseitig
   gegriffen hat - vorher unbemerkt, weil Client-seitige Auth-Checks
   das kaschiert haben.

2. Die CSP (mit dynamischem connect-src aus API_URL) lag in
   next.config.ts's headers() - die wird beim Docker-Build EINMAL
   aufgeloest und ins Standalone-Output eingefroren, nicht bei jedem
   Container-Start neu gelesen, wie im Code-Kommentar faelschlich
   behauptet. Jedes Deployment bekam dadurch permanent
   "connect-src http://localhost:3001" (den Fallback, da API_URL beim
   Build nicht gesetzt ist) - unabhaengig vom tatsaechlich im Pod
   gesetzten API_URL. Jede Anfrage vom Frontend ans Backend (Login,
   SSO-Status, Herkunftsseite) wurde dadurch von jedem Browser
   CSP-blockiert.

Fix: CSP + Security-Header ziehen nach proxy.ts (jetzt korrekt unter
src/) um - Proxy laeuft mit Node.js-Runtime frisch bei jeder Anfrage,
liest process.env.API_URL also korrekt zur Laufzeit. next.config.ts
behaelt nur noch die statische sw.js-Cache-Control-Regel.

Lokal end-to-end verifiziert: Build zeigt jetzt "ƒ Proxy (Middleware)"
(vorher komplett abwesend), CSP-Header zeigt nach Server-Start einen
distinct getesteten API_URL-Wert korrekt an (nicht mehr den
Localhost-Fallback), und ein echter Login-Klick im Browser schickt die
Anfrage jetzt an die richtige, zur Laufzeit gesetzte Backend-Origin.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Sonnet 5 committed 2026-09-02 21:44:23 +02:00
1 parent cd24523ea5
commit c95e1c4b18
3 files changed
+105 -81

No files matched your search

+9 -49
View File
@@ -1,44 +1,14 @@
import type { NextConfig } from "next";
// Runs when the Node server process starts inside the container, so this
// correctly reads the deployment's actual runtime API_URL (see
// src/lib/config/runtime-config.ts for why API_URL is a runtime env var
// rather than baked into the client bundle at build time) - this is server
// startup, not the browser bundle, so it isn't subject to that constraint.
function apiOrigin(): string {
try {
return new URL(process.env.API_URL ?? "http://localhost:3001").origin;
} catch {
return "";
}
}
// No CSP nonce here on purpose: nonce-based CSP requires every page to be
// dynamically rendered (Next.js CSP guide, "Static vs Dynamic Rendering"),
// which would drop this app's static optimization across the board. This
// policy still blocks the things that matter for a mostly-same-origin app -
// cross-origin exfiltration, clickjacking, base-tag/form hijacking - via
// 'unsafe-inline' only for script/style (needed for Next's own hydration
// data and Tailwind's inline styles), matching the same trade-off the
// backend's own helmet CSP already makes for style-src.
function cspHeader(): string {
return `
default-src 'self';
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' blob: data:;
font-src 'self';
connect-src 'self' ${apiOrigin()};
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'self';
upgrade-insecure-requests;
`
.replace(/\s{2,}/g, " ")
.trim();
}
// Security headers (CSP, X-Frame-Options, etc.) live in proxy.ts, not here.
// This file's headers() is resolved once at build time and frozen into the
// standalone output - fine for a static value like the sw.js rule below, but
// wrong for the CSP's connect-src, which needs the container's actual
// runtime API_URL. That mistake shipped a permanently-wrong connect-src
// (build-time env, if any, or the localhost fallback) to every environment
// regardless of the real deployed API_URL, silently CSP-blocking every API
// call including login. Proxy runs fresh on every request and reads env
// correctly - see proxy.ts for the real header logic.
const nextConfig: NextConfig = {
output: "standalone",
async headers() {
@@ -52,16 +22,6 @@ const nextConfig: NextConfig = {
source: "/sw.js",
headers: [{ key: "Cache-Control", value: "no-cache, no-store, must-revalidate" }],
},
{
source: "/:path*",
headers: [
{ key: "Content-Security-Policy", value: cspHeader() },
{ key: "X-Frame-Options", value: "SAMEORIGIN" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Referrer-Policy", value: "no-referrer" },
{ key: "Strict-Transport-Security", value: "max-age=31536000; includeSubDomains" },
],
},
];
},
};