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,145 @@
|
||||
/**
|
||||
* Vollständigkeitscheck der serverseitigen Modul-Durchsetzung (§3.4, Phase-1-Härtung).
|
||||
*
|
||||
* Jede mutierende Server-Action eines gegateten Moduls MUSS über einen
|
||||
* `moduleGuard("<key>")`-Guard laufen (siehe src/server/action-guard.ts), damit ein
|
||||
* für den Mandanten deaktiviertes Modul auch Writes serverseitig abweist.
|
||||
*
|
||||
* Dieses Script erzwingt das statisch: Es kennt die Zuordnung Action-Datei → Modul
|
||||
* und schlägt fehl (Exit 1 → Build/Test rot), sobald
|
||||
* - eine neue Action-Datei nicht zugeordnet ist ("vergessener Endpoint"),
|
||||
* - eine gegatete Datei den erwarteten moduleGuard nicht verwendet, oder
|
||||
* - eine exportierte Action nicht über `await guard(...)` läuft.
|
||||
*
|
||||
* Neue Action-Datei anlegen ⇒ hier eintragen (Modul-Key oder "EXEMPT").
|
||||
*/
|
||||
import { readdirSync, readFileSync } from "node:fs";
|
||||
import { join, dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { MODULE_KEYS } from "../src/lib/modules";
|
||||
|
||||
const ACTIONS_DIR = join(dirname(fileURLToPath(import.meta.url)), "..", "src", "server", "actions");
|
||||
|
||||
/** Zuordnung Action-Datei → Modul-Key. "EXEMPT" = kein gegatetes Fachmodul (eigene Auth). */
|
||||
const ACTION_MODULE: Record<string, string> = {
|
||||
"assets.ts": "assets",
|
||||
// M2 Strukturanalyse: primäre Informations-Assets (Dedup/Autocomplete) → Asset-Modul.
|
||||
"structure.ts": "assets",
|
||||
"processes.ts": "bia",
|
||||
"risks.ts": "risk",
|
||||
"risk-catalog.ts": "risk",
|
||||
"measures.ts": "measures",
|
||||
"tasks.ts": "tasks",
|
||||
"incidents.ts": "incidents",
|
||||
// IM-D: mandantenseitige Pflege der E-Mail-Intake-Konfiguration (moduleGuard("incidents") + tenant:manage).
|
||||
"incident-intake.ts": "incidents",
|
||||
"suppliers.ts": "suppliers",
|
||||
"services.ts": "suppliers",
|
||||
"software.ts": "suppliers",
|
||||
"projects.ts": "assets",
|
||||
"onboarding.ts": "onboarding",
|
||||
"onboarding-facts.ts": "onboarding",
|
||||
"onboarding-steps.ts": "onboarding",
|
||||
"onboarding-team.ts": "onboarding",
|
||||
"soa.ts": "onboarding",
|
||||
// AP3: ISO-Anwendbarkeitserklärung (eigenes Modul „soa").
|
||||
"soa-entries.ts": "soa",
|
||||
// AP4: Managementklauseln (Kennzahlen/Managementbewertung/CAPA) im Modul „review".
|
||||
"review.ts": "review",
|
||||
"gap.ts": "onboarding",
|
||||
// Audit-Vorbereitung — Modul `audit`.
|
||||
"audits.ts": "audit",
|
||||
"audit-evidence.ts": "audit",
|
||||
"control-descriptions.ts": "audit",
|
||||
"policies.ts": "policies",
|
||||
"policy-package.ts": "policies",
|
||||
// AP5: Dokumentenlenkung (Prüfzyklus, Neuversion/Historie, Lesebestätigung).
|
||||
"policy-control.ts": "policies",
|
||||
"policy-upload.ts": "policies",
|
||||
"hints.ts": "policies",
|
||||
"register.ts": "policies",
|
||||
// Plattform-Betrieb (eigene Auth) und Kunden-Einstellungen (tenant:manage) sind
|
||||
// keine per TenantModule gegateten Fachmodule — eigene Autorisierung, kein moduleGuard.
|
||||
"admin.ts": "EXEMPT",
|
||||
// SEC1: Mail-Betriebsfunktionen der Plattform-Administration (Auth über die
|
||||
// Plattform-Session), kein per TenantModule gegatetes Fachmodul.
|
||||
"mail.ts": "EXEMPT",
|
||||
// Backup-Portal: Enqueue-Actions für Portal-Restore/Export/DSGVO-Zustellung.
|
||||
// Betreiber-/Plattform-Fähigkeit (Auth über requirePlatformFullAdmin + MFA-Step-up),
|
||||
// kein per TenantModule gegatetes Fachmodul.
|
||||
"backup-admin.ts": "EXEMPT",
|
||||
"backup-settings.ts": "EXEMPT",
|
||||
// IM-D: Betreiber-Provisionierung/Verifizierung der Intake-Konfiguration + Inbound-Review.
|
||||
// Plattform-Fähigkeit (requirePlatformFullAdmin), kein per TenantModule gegatetes Fachmodul.
|
||||
"incident-intake-admin.ts": "EXEMPT",
|
||||
// SEC2: Passwort-Self-Service. Die Reset-Abläufe laufen bewusst OHNE Session
|
||||
// (der Nutzer ist ausgesperrt); abgesichert über Rate-Limit, Enumeration-
|
||||
// Neutralität und single-use-Tokens. Die angemeldeten Abläufe nutzen
|
||||
// requireSession bzw. requirePlatformSession.
|
||||
"auth-recovery.ts": "EXEMPT",
|
||||
"platform.ts": "EXEMPT",
|
||||
"platform-users.ts": "EXEMPT",
|
||||
"tenant-users.ts": "EXEMPT",
|
||||
"account.ts": "EXEMPT",
|
||||
"tenant-switch.ts": "EXEMPT",
|
||||
"webauthn.ts": "EXEMPT",
|
||||
"platform-admins.ts": "EXEMPT",
|
||||
"policy-templates.ts": "EXEMPT",
|
||||
"tenant-settings.ts": "EXEMPT",
|
||||
};
|
||||
|
||||
const errors: string[] = [];
|
||||
const files = readdirSync(ACTIONS_DIR).filter((f) => f.endsWith(".ts"));
|
||||
|
||||
for (const file of files) {
|
||||
const mapped = ACTION_MODULE[file];
|
||||
if (!mapped) {
|
||||
errors.push(
|
||||
`Nicht zugeordnete Action-Datei: ${file} — in scripts/check-module-guards.ts eintragen (Modul-Key oder "EXEMPT").`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const src = readFileSync(join(ACTIONS_DIR, file), "utf8");
|
||||
|
||||
if (mapped === "EXEMPT") {
|
||||
// Auth-Nachweis: entweder ein require*-Guard ODER ein direkter auth()-Aufruf
|
||||
// (z. B. tenant-switch.ts, das im No-Tenant-Zustand kein requireSession nutzen
|
||||
// kann, aber die Identity + Mitgliedschaftszugehörigkeit selbst prüft).
|
||||
if (!/require(Session|Platform\w*|Permission)|\bauth\(\)/.test(src)) {
|
||||
errors.push(`${file}: als EXEMPT markiert, aber keine erkennbare Auth-Prüfung.`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!MODULE_KEYS.includes(mapped)) {
|
||||
errors.push(`${file}: unbekannter Modul-Key "${mapped}" (nicht in src/lib/modules.ts).`);
|
||||
}
|
||||
if (!src.includes(`moduleGuard("${mapped}")`)) {
|
||||
errors.push(`${file}: erwartet moduleGuard("${mapped}") — Modul-Gating fehlt oder falscher Key.`);
|
||||
}
|
||||
|
||||
// Jede exportierte Server-Action muss über await guard(...) laufen.
|
||||
const exportRe = /export async function (\w+)\s*\(/g;
|
||||
const positions: { name: string; index: number }[] = [];
|
||||
let m: RegExpExecArray | null;
|
||||
while ((m = exportRe.exec(src))) positions.push({ name: m[1], index: m.index });
|
||||
for (let i = 0; i < positions.length; i++) {
|
||||
const start = positions[i].index;
|
||||
const end = i + 1 < positions.length ? positions[i + 1].index : src.length;
|
||||
if (!/await guard\(/.test(src.slice(start, end))) {
|
||||
errors.push(
|
||||
`${file}: Action "${positions[i].name}" läuft nicht über await guard(...) — Modul-/Rechte-Guard fehlt.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length) {
|
||||
console.error("✗ Modul-Guard-Vollständigkeitscheck fehlgeschlagen:");
|
||||
for (const e of errors) console.error(" - " + e);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(
|
||||
`✓ Modul-Guard-Vollständigkeitscheck: ${files.length} Action-Dateien geprüft — alle mutierenden Actions sind modul- und rechtegegated.`
|
||||
);
|
||||
Reference in New Issue
Block a user