← All modules
security

Audit

Live

Comprehensive audit logging for all admin actions with before/after diffs.

event.type === "audit" modules/audit
Overview

What Audit does

Every admin-plane action — config edits, secret rotations, deploy approvals — is captured as a signed audit record with before/after snapshots and the responsible actor. The chain is append-only and tamper-evident: each row is hash-linked to the previous one, so a single byte changed downstream invalidates everything after it.

The Hono adapter wires this into your HTTP layer for free. Every authenticated request emits an `audit` event with the resource type, the resource id, and a redacted snapshot of the request body. Sensitive headers (Authorization, Cookie, X-Cuitty-Signature) and payload fields (password, secret, token, apiKey) are stripped before the event leaves your process.

Wire payload

Same shape, three syntaxes

The wire protocol is plain HTTP, plain JSON, HMAC-SHA256. The TypeScript tab uses the SDK; the cURL tab is the raw HTTP equivalent; the Python tab shows the preview SDK shape.

import { createCuittyClient } from "@cuitty/sdk";
import { auditPlugin } from "@cuitty/sdk/plugins/audit";

const cuitty = createCuittyClient({
  portalUrl: "https://app.cuitty.com",
  projectId: process.env.CUITTY_PROJECT_ID!,
  apiKey: process.env.CUITTY_API_KEY!,
});
cuitty.use(auditPlugin());
cuitty.start();

await cuitty.emit({
  type: "audit",
  timestamp: new Date().toISOString(),
  data: {
    actor: "alice@example.com",
    action: "secret.rotate",
    resourceType: "secret",
    resourceId: "stripe.live_key",
    method: "POST",
    path: "/api/secrets/stripe.live_key/rotate",
    statusCode: 200,
    duration: 142,
  },
});
SDK plugin

Drop-in plugin

@cuitty/sdk/plugins/audit
import { auditPlugin } from "@cuitty/sdk/plugins/audit";

cuitty.use(auditPlugin({
  excludeRoutes: ["/health", "/metrics"],
  redactBodyFields: ["password", "secret", "token"],
}));
Storage

Database schema

Excerpt from modules/audit/schema.sql. One libSQL file per module — back it up with cp.

CREATE TABLE IF NOT EXISTS audit_logs (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id TEXT NOT NULL,
  user_email TEXT,
  module_id TEXT NOT NULL,
  action TEXT NOT NULL,
  resource_type TEXT NOT NULL,
  resource_id TEXT NOT NULL,
  before_state TEXT,
  after_state TEXT,
  ip_address TEXT,
  request_method TEXT,
  request_path TEXT,
  status_code INTEGER,
  duration_ms INTEGER,
  timestamp INTEGER DEFAULT (unixepoch())
);

Related modules