Audit ledger

A tamper-evident audit log, by construction.

Every audit entry is cryptographically chained to all the history before it. Alter, delete, reorder, or backdate any past event and the chain no longer verifies — and a periodic, signed anchor catches even a full rewrite.

The hash chain

Each entry commits to every entry before it

On write, an entry's hash is computed over the previous entry's hash plus the entry's own immutable fields. That single link is what makes the whole sequence tamper-evident.

Entry n − 1audit_log
prevHash
…7a2e
hash
c41b…
Entry naudit_log
prevHash
c41b…
hash
9f3c…
Entry n + 1audit_log
prevHash
9f3c…
hash
e08d…

hash = sha256( prevHash ‖ canonical(entry) ) — where canonical() is key-sorted JSON, so the same content always hashes the same way. The first entry links to a fixed GENESIS value. Appends are serialized per tenant with a Postgres advisory lock, and the position seq is derived from the live chain head — so a dropped best-effort write never leaves a gap.

Threat model

What the chain detects

Edit a past event

The entry's recomputed hash no longer matches its stored hash.

Delete an event

A gap in seq, and the following entry's prevHash no longer lines up.

Reorder / backdate

Position is bound into each hash; any move breaks the link.

Insert into the past

Every subsequent hash changes — the break cascades forward to the head.

Even if an attacker recomputes the hash of the row they edited, the next entry’s prevHash still points at the original — so the tamper surfaces one link later. To hide it they’d have to rewrite the entire chain forward from the edit. That’s exactly what anchoring closes off.

Periodic head anchor

Closing the full-rewrite gap

A hash chain is detection, not prevention — someone with full control of the database and app could recompute the chain end-to-end. So Quadrazene periodically snapshots the chain's head.

Chain head
latest entry hash + seq
Anchor
snapshot + HMAC signature
Retained out-of-band
SIEM · WORM · export

Each anchor records the head hash, its sequence number, and an entry count — optionally HMAC-signed with a key the app server doesn’t hold. Once an anchor is retained somewhere the app can’t reach back into, a later rewrite of the chain will no longer match the anchored value. Anchoring runs on the scheduler (default every six hours) and on demand.

Verification

Re-derive integrity at any time

Walk the chain

Verification re-reads every entry in order, recomputes each hash from the prior link, confirms the sequence is contiguous, and checks every retained anchor against the chain at its recorded position. It returns intact, or the exact seq and reason of the first break.

# intact
{ "ok": true, "entriesChecked": 1284,
"anchorsChecked": 14, "head": "9f3c…" }
# tampered
{ "ok": false, "brokenAtSeq": 742,
"reason": "entry hash mismatch — content altered" }

By design

What's covered

  • Per-tenant chains
    Each tenant has an independent chain and its own anchors; isolation is preserved.
  • Append-only by integrity
    Not just convention — the chain makes after-the-fact edits detectable.
  • No silent gaps
    seq comes from the real head, so a failed write doesn't fabricate a gap.
  • Signed anchors
    Optional HMAC signing puts the anchor of trust outside the app server.
  • Detection, then prevention
    Pair with WORM storage / restricted DB perms to stop edits, not just catch them.
  • Exportable
    Entries and anchors export for an auditor or SIEM to re-verify independently.