import { globSync } from "node:fs"; import { readFileSync } from "node:fs"; import { dirname, relative, resolve } from "node:path"; import { fileURLToPath } from "node:url"; /** * Statischer Default-Deny-Scanner für Server Actions (Querschnittsstandard 3). * * Reine Funktionen, damit der Beweis sowohl im Vitest-Unit-Test (offline, ohne * Server/DB) als auch in der Playwright-Suite (`server-actions-guard.spec.ts`) * wiederverwendet werden kann. */ const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..", ".."); /** Erkennt einen der fünf kanonischen Guards (guards.ts). */ export const GUARD_REGEX = /require(Session|Role|OwnBrigade|PlatformAdmin|WehrAdmin)\s*\(/; /** * Genuin öffentliche "use server"-Funktionen (Login VOR der Authentifizierung). * Diese DÜRFEN keinen Session-Guard haben — sie sind der Einstieg. Schlüssel: * `:`. */ export const PUBLIC_ACTION_ALLOWLIST: ReadonlySet = new Set([ "src/app/(auth)/login/actions.ts:loginAction", "src/app/(auth)/login/actions.ts:authentikLoginAction", ]); /** * Prüft den Quelltext EINER Datei. Gibt eine Liste von Verstößen * (`: `) zurück. Dateien ohne "use server"-Direktive * liefern nie Verstöße. */ export function findUnguardedActions( file: string, src: string, allowlist: ReadonlySet = PUBLIC_ACTION_ALLOWLIST, ): string[] { if (!src.includes('"use server"')) return []; const offenders: string[] = []; const fns = src.split(/export async function /).slice(1); for (const body of fns) { const name = body.slice(0, body.indexOf("(")).trim(); if (allowlist.has(`${file}:${name}`)) continue; // Erste ~600 Zeichen des Funktionskörpers müssen einen Guard enthalten. if (!GUARD_REGEX.test(body.slice(0, 600))) { offenders.push(`${file}: ${name}`); } } return offenders; } /** * Scannt das echte Repository (src/**) nach ungeschützten Server Actions. */ export function findUnguardedActionsInRepo(): string[] { const files = globSync("src/**/*.{ts,tsx}", { cwd: REPO_ROOT }); const offenders: string[] = []; for (const rel of files) { const abs = resolve(REPO_ROOT, rel); const src = readFileSync(abs, "utf8"); if (!src.includes('"use server"')) continue; const repoRel = relative(REPO_ROOT, abs); offenders.push(...findUnguardedActions(repoRel, src)); } return offenders; }