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>
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Fail-Secure-Startprüfung der sicherheitskritischen Umgebungsvariablen (F-01, Teil 3).
|
||||
*
|
||||
* WARUM LAZY UND MEMOISIERT — bitte NICHT zu einem Import-Zeit-`throw` "aufräumen":
|
||||
* Zur Docker-Build-Zeit lädt `next build` alle Module, aber es sind KEINE echten
|
||||
* Secrets gesetzt — `DATABASE_URL` ist in der builder-Stage nur ein Platzhalter
|
||||
* (siehe Dockerfile) und `AUTH_SECRET` ist überhaupt nicht gesetzt. Ein `throw` auf
|
||||
* Modulebene würde daher jeden Build zerlegen. Deshalb prüft `assertSecureEnv()`
|
||||
* erst beim ersten TATSÄCHLICHEN Auth-Zugriff (aus `requireSession()`,
|
||||
* `requirePlatformSession()` und den beiden `authorize()`-Callbacks heraus) und
|
||||
* merkt sich das Ergebnis, statt bei jedem Aufruf neu zu prüfen.
|
||||
*
|
||||
* Ziel: Fehlende oder zu kurze Secrets führen zu einem harten Abbruch der
|
||||
* Auth-Verarbeitung (fail secure) statt zu einem stillen Weiterlaufen, bei dem
|
||||
* Auth.js bei Fehlkonfiguration ein Session-Objekt mit Fehlerzustand liefern
|
||||
* könnte (fail open, CWE-636).
|
||||
*/
|
||||
|
||||
// Mindestlänge für Secrets (Auth.js empfiehlt >= 32 zufällige Zeichen; 16 ist die
|
||||
// harte Untergrenze, unter der wir den Start als Fehlkonfiguration werten).
|
||||
const MIN_SECRET_LENGTH = 16;
|
||||
|
||||
let verified = false;
|
||||
|
||||
/**
|
||||
* Prüft einmalig (memoisiert), dass die sicherheitskritischen Umgebungsvariablen
|
||||
* gesetzt und plausibel sind. Wirft bei Fehlkonfiguration, sonst still.
|
||||
*/
|
||||
export function assertSecureEnv(): void {
|
||||
if (verified) return;
|
||||
|
||||
// Auth.js v5 nutzt AUTH_SECRET; NEXTAUTH_SECRET wird als Fallback akzeptiert,
|
||||
// damit bestehende Deployments nicht fälschlich als unsicher gewertet werden.
|
||||
const authSecret = process.env.AUTH_SECRET ?? process.env.NEXTAUTH_SECRET;
|
||||
if (!authSecret || authSecret.length < MIN_SECRET_LENGTH) {
|
||||
throw new Error(
|
||||
`Startabbruch: AUTH_SECRET fehlt oder ist zu kurz (mind. ${MIN_SECRET_LENGTH} Zeichen). ` +
|
||||
"Fail-Secure statt Fail-Open — Auth-Zugriff wird abgebrochen.",
|
||||
);
|
||||
}
|
||||
|
||||
const databaseUrl = process.env.DATABASE_URL;
|
||||
if (!databaseUrl || databaseUrl.length < MIN_SECRET_LENGTH) {
|
||||
throw new Error("Startabbruch: DATABASE_URL fehlt oder ist ungültig.");
|
||||
}
|
||||
|
||||
// Härtung §1: PASSWORD_PEPPER ist Pflicht (32-Byte hex = 64 Hexzeichen). Er wird
|
||||
// in jeden Argon2-Hash/-Verify gemischt (src/server/password.ts) und ist NICHT aus
|
||||
// AUTH_SECRET ableitbar (eigenständiges, nicht rotierbares Umgebungs-Secret).
|
||||
const pepper = process.env.PASSWORD_PEPPER ?? "";
|
||||
if (!/^[0-9a-fA-F]{64}$/.test(pepper)) {
|
||||
throw new Error(
|
||||
"Startabbruch: PASSWORD_PEPPER fehlt oder ist kein 32-Byte-Hex (64 Hexzeichen). " +
|
||||
"Erzeuge ein pro-Umgebung eindeutiges Secret: openssl rand -hex 32. Fail-Secure.",
|
||||
);
|
||||
}
|
||||
|
||||
verified = true;
|
||||
}
|
||||
Reference in New Issue
Block a user