Audit & Sicherheit
Drei Features für Compliance, Secret-Management und Schutz vor runaway-Workloads.
audit
Status: ✅ Stable
Wofür: Jeder Write durch die Pipeline (Create/Update/Delete) wird
in audit_log festgehalten — wer, wann, was, mit before/after-Diff.
Kommt out-of-the-box, ohne dass deine Handler etwas davon merken
müssen.
Wie es funktioniert: Wird als postSave-System-Hook (Priorität
1002) registriert. Bekommt vom Lifecycle-Pipeline den SaveContext
({ id, data, changes, previous, isNew, user }) und schreibt eine
Row pro Entity-Mutation. Liest sich später über r.queryHandler —
oder du baust dir ein Audit-Dashboard über die AuditQueries-Konstante.
Performance: Async-Append, in derselben Transaktion wie der Write. Kein zusätzlicher Round-Trip, aber das Audit-Schreiben kann ein Boot verlangsamen, wenn die Tabelle riesig ist und Indizes fehlen — nutze einen Partition-by-Month-Setup für hohe Volumen-Tenants.
Beispiel:
import { createAuditFeature, AuditQueries } from "@kumiko/bundled-features/audit";
await runDevApp({ features: [createAuditFeature(), myFeature],});
// Custom Query: letzte 10 Audit-Events des aktuellen Tenantsr.queryHandler({ qn: "admin:audit:recent", handler: async (ctx) => { return ctx.dispatcher.query(AuditQueries.list, { tenantId: ctx.tenantId, limit: 10, orderBy: { createdAt: "desc" }, }); },});secrets
Status: ✅ Stable
Wofür: Per-Tenant verschlüsselte Werte — API-Keys für externe Services, OAuth-Client-Secrets, Webhook-Tokens. Verschlüsselt mit einem Per-Tenant-Key, der wiederum mit deinem Master-Key encrypted ist (envelope encryption).
Wie es funktioniert: r.secret({ key: "stripe.api_key" })
deklariert einen Secret-Slot. Der Operator schreibt einen Wert über die
Admin-UI oder direkt in tenant_secrets (encrypted). Im Handler-Code:
const stripe = await ctx.secrets.read("stripe.api_key"). Jeder Read
emit’d ein tenant.secret.read-Event — du siehst im Audit-Trail,
welcher Code wann auf welchen Secret-Slot zugegriffen hat.
Rotation: Eingebaut. rotateJob re-encryptet alle Secrets eines
Tenants gegen einen neuen Master-Key — als Background-Job, ohne
Downtime. Macht ein Per-Tenant-Master-Key-Compromise zur einfachen
Op-Aktion statt Disaster.
secrets vs config encrypted:
config ist für Settings, die der Operator pflegt (SMTP-Pass aus dem
Settings-Dialog), secrets ist für externe Service-Credentials mit
Rotations- und Audit-Anforderung (Stripe-API-Key, OAuth-Client-Secret).
Wenn ein Compliance-Auditor fragt „wer hat wann auf den Stripe-Key
zugegriffen” — das ist secrets-Territorium.
Recipe: samples/recipes/encrypted-tenant-config
zeigt das End-to-End: Schema, Write, Read, Rotation.
Beispiel:
import { createSecretsFeature } from "@kumiko/bundled-features/secrets";
await runDevApp({ features: [createSecretsFeature(), myFeature],});
// Secret-Slot deklarieren + lesenexport const stripeFeature = defineFeature("stripe", (r) => { r.secret({ key: "stripe.api_key" });
r.writeHandler({ qn: "stripe:charge", handler: async (ctx, { amount }) => { const apiKey = await ctx.secrets.read("stripe.api_key"); const stripe = new Stripe(apiKey); return stripe.charges.create({ amount, currency: "eur" }); }, });});rate-limiting
Status: ✅ Stable
Wofür: Per-Tenant + Per-User Throttle auf Write-Pfade. Schützt gegen Bot-Attacks, runaway Scripts, fehlerhafte Integrations, die einen Endpoint hämmern.
Wie es funktioniert: Sliding-Window in Redis. Pro
Handler-Invocation checkt eine Pre-Hook, ob der Counter unter dem
Limit liegt; wenn nicht → rate_limited-Error mit retry-after.
Defaults: 60 Writes/Minute pro User, 600 pro Tenant — pro Handler
überschreibbar via
r.writeHandler({ ..., rateLimit: { perUserPerMinute: 5 } }).
Wann eigene Limits: Login-Endpoint sollte härter sein (5/min/IP), Public-Read-Endpoints brauchen oft gar kein Rate-Limiting. Implementation läuft via Lua-Script in Redis, atomar.
Beispiel:
import { createRateLimitingFeature } from "@kumiko/bundled-features/rate-limiting";
await runDevApp({ features: [createRateLimitingFeature(), myFeature],});
// Härteres Limit pro Handler (Login: 5/min/User)r.writeHandler({ qn: "auth:login", rateLimit: { perUserPerMinute: 5 }, handler: async (ctx, payload) => { /* ... */ },});