Tamper-evident chain design
Part of the Universal Audit Log Specification. The cryptographic hash chain across the
audit.audit_entriestable — each row'shashisH(prev_hash || canonical_payload), so any tampered or out-of-order row breaks the chain at the next verification scan. This is what makes the audit log forensically reliable.
11. Tamper-Evident Chain Design
11.1 Overview
Tamper-evident chaining provides cryptographic proof that audit entries have not been modified, reordered, or deleted after creation. The chain is per-tenant to avoid global serialisation bottlenecks.
11.2 Hash Algorithm
SHA-256 SHALL be used for all chain hashes.
11.3 Hash Computation
entry_hash = SHA256(
previous_hash ||
tenant_id ||
actor_id ||
action ||
resource_type ||
resource_id ||
SHA256(changes) ||
created_at
)
The changes field is hashed separately to bound the input size.
11.4 Chain Head Management
An audit.chain_heads table tracks the latest hash per tenant:
CREATE TABLE audit.chain_heads (
tenant_id UUID PRIMARY KEY, last_hash TEXT NOT NULL,
last_entry_id UUID NOT NULL, updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
Concurrency: SELECT ... FOR UPDATE on the tenant's chain_heads row serialises hash computation per tenant. Alternative for high-throughput tenants: assign a per-tenant sequence number at write time, then compute hashes in a background job that processes entries in sequence order (decouples chaining from the write path).
11.5 Integrity Verification
A nightly job SHALL walk each tenant's chain: load entries in created_at order, recompute each entry_hash, and compare against the stored value. On mismatch, alert immediately and record the break point.
11.6 Chain Recovery After PITR
After point-in-time recovery, identify the last verified hash ("known-good" point), re-seal all subsequent entries by recomputing hashes forward, and record the re-seal event in the audit log.