Audit trail

Verify the audit chain

Verify the audit chain for tenant admins and governance reviewers.

For: tenant admins and governance reviewers.

A chain that shows edits

Each tenant's entries form a chain, and the cluster's own entries another. Every entry has a sequence number, the time the database clock recorded it, and two hashes: the SHA-256 of its content, and the SHA-256 of the previous entry's hash and its own content hash. Change an entry, delete one, or slip one in, and the chain breaks where it happened.

Terminal
oz0 audit verify
Output
CHAIN      STATUS  ENTRIES  REDACTED  HEAD  HEAD HASH     PROBLEM
(cluster)  ok      12       0         12    5be0d6a1…
default    BROKEN  1        0         45    9c41fe02…     entry 2 (01a1000b-e17e-7627-b463-c144bc77f73d) was edited: its actor does not match its hash

audit verify exits with an error when a chain is broken, so you can run it from a scheduled check. It checks each chain up to the head it finds when it starts, so a busy tenant whose jobs add entries meanwhile verifies as usual. Cluster admins verify every chain; a tenant's admins verify their tenant's with --tenant. Each verification is in the log too.

Redacted entries. An erasure and retention's anonymising blank some entries' actor, target, detail, private part and starter. Each of those parts has its own hash, salted with a random salt the entry keeps until it is redacted, and the content hash covers these hashes and the entry's other fields (sequence number, ID, times, tenant, action, subject, agent) as they are.

A redacted entry keeps its hashes, so everything it still shows is checked, and without the salt its hashes say nothing about what it held. The erasure or retention entry written with the redaction lists the entries it redacted, and audit verify fails on a redacted entry that no such entry lists: marking an entry redacted by hand does not hide an edit.

The chain makes the log append-only in practice: anyone who can write to the database can still rewrite the whole chain from the edit on. Keep the head of each chain somewhere else (an export, a ticket, a signed message) and a later rewrite shows: the head no longer matches.

On this page
Copyright © 2026