security(cards): fail-secure dev-stub, headers, rate-limit, dsgvo audit
Some checks are pending
CI / validate (push) Waiting to run
Some checks are pending
CI / validate (push) Waiting to run
Behebt live verifiziertes Auth-Bypass auf cardecky-api.mana.how (X-User-Id → founder-Tier) und zieht im selben Patch das fehlende Operations-/Compliance-Fundament nach. * Auth-Middleware fail-secure: opt-in via CARDS_AUTH_DEV_STUB="true" (war opt-out, Default true). Compose-Default flipped auf "false", NODE_ENV="production" für cards-api ergänzt, env-Template dokumentiert. vitest.config.ts + tests/setup.ts aktivieren den Stub gezielt für Test-Suiten. * Security-Headers: Hono secureHeaders() in apps/api, SvelteKit hooks.server.ts mit X-Frame/X-Content-Type/Referrer/ HSTS in apps/web. CSP bewusst ausgelassen — eigener Sprint. * CORS-localhost-Whitelist nur außerhalb Prod. * Rate-Limiting (in-memory sliding window, dependency-frei) auf share.receive 60/min/IP, media.upload 30/min/user, decks.generate + decks.from-image 10/min/user, dsgvo.* 10/min/IP. * Health-Endpoint mit echter DB- und MinIO-Probe; /healthz bleibt Liveness, /healthz/details ist Readiness mit 503 bei Failure. * DSGVO-Honesty: storage_ok + storage_error im Response (statt schluckend console.warn), Account-UI zeigt Fehler-Toast. * Audit-Log: strukturierte JSON-Zeile (kind: "audit") auf stdout für /dsgvo/export, /dsgvo/delete, /me/export, /me/delete. * Bug-Fix: duplizierte case "multiple-choice"-Clause in fsrs.ts. Verifiziert: apps/api 17 Files / 104 Tests grün, apps/web check 0 errors. Deploy auf Mac Mini steht noch aus. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5859e202c5
commit
e1ddbf34b3
21 changed files with 832 additions and 80 deletions
34
apps/api/src/lib/audit.ts
Normal file
34
apps/api/src/lib/audit.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* Audit-Log für sicherheits-/compliance-relevante Aktionen.
|
||||
*
|
||||
* Schreibt eine strukturierte JSON-Zeile auf stdout (`console.info`).
|
||||
* In Produktion landet das im Container-Log und kann später per
|
||||
* Promtail/Loki oder ähnlich aggregiert werden. Persistierung in
|
||||
* eine `audit_log`-Tabelle ist ein eigener Sprint — wenn die fällig
|
||||
* wird, muss nur diese Funktion erweitert werden, alle Aufrufer
|
||||
* bleiben gleich.
|
||||
*
|
||||
* Ereignis-Naming-Konvention: `<domäne>.<aktion>`, lower_snake_case.
|
||||
* Bsp.: `dsgvo.export`, `dsgvo.delete`, `auth.bypass_attempt`.
|
||||
*/
|
||||
|
||||
export interface AuditEvent {
|
||||
event: string;
|
||||
actor_user_id?: string;
|
||||
target_user_id?: string;
|
||||
auth_mode?: 'jwt' | 'dev-stub' | 'service-key';
|
||||
ip?: string | null;
|
||||
user_agent?: string | null;
|
||||
result: 'success' | 'failure' | 'partial';
|
||||
detail?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export function auditLog(event: AuditEvent): void {
|
||||
const line = {
|
||||
kind: 'audit',
|
||||
ts: new Date().toISOString(),
|
||||
...event,
|
||||
};
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(JSON.stringify(line));
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue