Zum Inhalt springen

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 Tenants
r.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 + lesen
export 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) => { /* ... */ },
});

Siehe auch