Audit log
Append-only record of state-changing admin actions — who did what, when, against what target.
The audit log captures every state-changing call to /api/v1/admin/* —
settings updates, hook saves, admin invites, backup triggers, and so on —
every collection write at /api/v1/collections, and every MCP tool call. One row per request, append-only, designed for
compliance and forensic lookups.
What gets recorded
Section titled “What gets recorded”A row is written for every POST / PUT / PATCH / DELETE to
/api/v1/admin/* and to /api/v1/collections (create, update, delete a
collection). GET is not audited — read traffic is high-volume and
already covered by the request log.
Skip-list (intentionally not audited):
/api/v1/admin/auth/login,/api/v1/admin/auth/logout— auth attempts already log via the request log/api/v1/admin/setup— fresh-install bootstrap/api/v1/admin/migrations/diff— read-only despite being POST- Any path matching
/api/v1/admin/preview-*or/api/v1/admin/.*/preview$
Each row is immutable. The application never updates audit rows, and
deletes them only through the opt-in audit.retention_days setting (entries
older than that many days are removed by the daily cleanup; 0, the default,
keeps them forever). Operators can also DELETE directly from the
vaultbase_audit_log table.
Schema
Section titled “Schema”CREATE TABLE vaultbase_audit_log ( id TEXT PRIMARY KEY, actor_id TEXT, -- admin id from the JWT actor_email TEXT, -- email cached at the time of action method TEXT NOT NULL, path TEXT NOT NULL, action TEXT NOT NULL, -- logical label, e.g. "collections.create" target TEXT, -- best-effort id or name from the path status INTEGER NOT NULL, ip TEXT, -- client IP (see IP capture); null for MCP rows summary TEXT, -- optional human / JSON at INTEGER NOT NULL -- unix-seconds);Indexes on (actor_id), (at DESC), (action).
Action labels
Section titled “Action labels”action is derived from the path + method. Examples:
| Method | Path | action |
|---|---|---|
| POST | /api/v1/collections | collections.create |
| PATCH | /api/v1/collections/<id> | collections.update (target = the id) |
| PATCH | /api/v1/admin/settings | settings.update |
| POST | /api/v1/admin/hooks | hooks.create |
| POST | /api/v1/admin/admins | admins.create |
| POST | /api/v1/admin/sql/run | sql.create (target = run; the SQL text is not stored) |
Uncategorised paths fall back to <METHOD> <path>.
MCP tool calls are entries with action mcp.tool, method MCP and the
tool name as path; run_sql writes mcp.sql.read / mcp.sql.write. See
MCP audit trail.
Read API
Section titled “Read API”GET /api/v1/admin/audit-log ?page=1&perPage=50 &actorId=<id> &actionPrefix=collections. &from=<unix-seconds>&to=<unix-seconds>Returns:
{ "data": { "data": [ { "id": "...", "actor_email": "ops@acme", "action": "collections.delete", ... } ], "page": 1, "perPage": 50, "totalItems": 1234, "totalPages": 25 }}Admin UI
Section titled “Admin UI”Sidebar → System → Audit log. Filter by actor id, action prefix, and date range; click a row to see the full entry (path, status, target, IP, timestamp, summary). The table paginates server-side; default page size 50.
IP capture
Section titled “IP capture”The audit row’s ip field is the connecting peer’s address.
X-Forwarded-For is used only when the peer is a trusted proxy
(VAULTBASE_TRUSTED_PROXIES or security.trusted_proxies). Without that, a
client can’t put a fake address in the column by sending the header. MCP tool
calls have no request to read from, so their ip is null.
Environment=VAULTBASE_TRUSTED_PROXIES=10.0.0.0/8,127.0.0.1/32Same env var the rate-limiter uses for its real-IP resolution.