Audit & security
Three features for compliance, secret management and protection against runaway workloads.

Status: ✅ Stable
What: Every write through the pipeline (create/update/delete) is recorded for compliance review, who, when, what, with before/after context. Comes out of the box; your handlers don’t need to know about it.
How it works: Registered as a postSave system hook. Receives the
SaveContext from the lifecycle pipeline and appends an audit event.
Operators browse via the built-in audit screens, or call
AuditQueries.list / AuditQueries.details from another handler.
Example:
import { createAuditFeature, AuditQueries } from "@cosmicdrift/kumiko-bundled-features/audit";import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";import { z } from "zod";
await runDevApp({ features: [createAuditFeature(), myFeature],});
// Custom query: recent audit rows for the current tenantdefineFeature("ops", (r) => { r.queryHandler( "recent-audit", z.object({}), async (_query, ctx) => ctx.query(AuditQueries.list, { limit: 10, }), { access: { roles: ["Admin"] } }, );});secrets
Section titled “secrets”Status: ✅ Stable
What: Per-tenant encrypted values, API keys for external
services, OAuth client secrets, webhook tokens. Envelope-encrypted at
rest under a platform master key (KUMIKO_SECRETS_MASTER_KEY_V1, with
versioned successors for rotation).
How it works: r.secret(name, opts) (or the object form
r.secret({ name, … })) declares a typed secret handle. Operators set
values via secrets:write:set. In handlers:
const branded = await ctx.secrets.get(tenantId, handle, auditCtx?) :
then branded.reveal() for the plaintext. Prefer
requireSecretsContext(ctx, handlerName) so every .get carries the
audit user + handler automatically. Reads append a tenantSecretRead
event.
Rotation: a built-in rotate job re-encrypts envelopes after a KEK
version bump (KUMIKO_SECRETS_MASTER_KEY_V2, … +
KUMIKO_SECRETS_MASTER_KEY_CURRENT_VERSION).
secrets vs config:
config is for settings operators maintain (SMTP host from a settings
dialog); secrets is for external credentials with rotation and
read-audit requirements (Stripe API key). When a compliance auditor asks
“who accessed the Stripe key when”, that’s secrets.
Recipe: samples/recipes/secrets-demo
Example:
import { createSecretsFeature, requireSecretsContext } from "@cosmicdrift/kumiko-bundled-features/secrets";import { defineFeature, defineWriteHandler, type SecretKeyHandle } from "@cosmicdrift/kumiko-framework/engine";import { z } from "zod";
await runDevApp({ features: [createSecretsFeature(), createBillingFeature()],});
let STRIPE_API_KEY: SecretKeyHandle;
const chargeWrite = defineWriteHandler({ name: "charge", schema: z.object({ amount: z.number().positive() }), access: { roles: ["TenantAdmin"] }, handler: async (event, ctx) => { const secrets = requireSecretsContext(ctx, "billing:write:charge"); const branded = await secrets.get(event.user.tenantId, STRIPE_API_KEY); const apiKey = branded!.reveal(); // use apiKey in-memory only, never return it to the client return { isSuccess: true, data: { charged: event.payload.amount } }; },});
export function createBillingFeature() { return defineFeature("billing", (r) => { STRIPE_API_KEY = r.secret("stripe.apiKey", { label: { en: "Stripe API Key" }, scope: "tenant", }); r.writeHandler(chargeWrite); });}rate-limiting
Section titled “rate-limiting”Status: ✅ Stable
What: Per-handler throttle (L3) wired by the dispatcher when a handler
declares rateLimit. Optional ops introspection via
rate-limiting:query:status. Protects login, expensive search, and
other hot paths.
How it works: Sliding window (typically Redis-backed when configured
on the server). Declare limits as
{ per: "user" | "tenant" | "ip" | "user+handler" | "tenant+handler" | "ip+handler", limit, windowSeconds } on the
handler options. The rate-limiting feature itself is optional if you
only need the L3 option and no status query.
Recipe: samples/recipes/rate-limiting
Example:
import { createRateLimitingFeature } from "@cosmicdrift/kumiko-bundled-features/rate-limiting";import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";import { z } from "zod";
await runDevApp({ features: [createRateLimitingFeature(), createRateLimitShowcaseFeature()],});
export function createRateLimitShowcaseFeature() { return defineFeature("rl-showcase", (r) => { r.queryHandler( "expensive-search", z.object({ q: z.string().min(1) }), async ({ payload }) => ({ q: payload.q, hits: 0 }), { access: { roles: ["Admin", "User"] }, rateLimit: { per: "user", limit: 3, windowSeconds: 60 }, }, ); });}