← All posts

A tamper-evident audit trail

An agent's authorization decisions are recorded for later. An incident responder reconstructs what the agent did. A customer asks why an action was blocked. An auditor checks that a control held. Each of those depends on the record being complete and unchanged since it was written.

A table of log rows can't show that on its own. If an attacker reaches the database, deleting the row that recorded their activity is a normal way to cover tracks, and the table reads fine afterward. AgentTrust ID records decisions as a hash chain so that a removed or altered entry is detectable.

How the chain works

Every session starts with a genesis hash of 64 zeros. Each authorization check appends one entry. The entry is the decision: the check ID, the action name, its effect, and the result. The new chain hash is the SHA-256 of the previous hash followed by that entry:

H_new = SHA-256(H_old || entry)

The session stores the current hash and the list of entries in the order they were added. That ordering is what lets the chain be replayed later.

Why an edit shows up

Each hash folds in every entry before it, so the final hash depends on the whole history in order. Change the effect on one past decision and that entry hashes differently. Every hash after it changes too. The stored head no longer matches what the entries produce.

The same holds for deletion and insertion. Drop an entry and the replay comes up short. Add one and it doesn't fit the sequence. There is no way to edit the middle of the chain and leave the head intact.

What verification checks

The verify endpoint replays the chain from the genesis hash using the recorded entries, then compares the result to the stored head. A match means the entries are the exact sequence that produced the head.

It also counts. A healthy session has one entry per recorded call, so the endpoint checks that the entry count equals the session's call count. This catches a decision that was counted but missing from the entry list, which a replay alone would accept.

Two more checks run alongside it. The session's scope hash still has to match its current scope. The head has to match a hash you supply, if you're comparing against a known-good value saved earlier.

Verification fails closed. A malformed head, a replay that doesn't reproduce the head, or a count mismatch all return not valid.

Why it's worth the mechanism

Three things come out of recording decisions this way.

An attacker who reaches the store can't quietly trim the log. Removing the entry that recorded their action breaks the replay, so the gap is visible instead of silent.

You can show the record is complete to someone who doesn't trust your operators. The check is deterministic, so a customer or auditor re-running it gets the same answer regardless of who had database access.

The count check also catches loss that isn't an attack. A decision dropped by a concurrency bug, counted but never added to the entry list, fails the same check. The mechanism flags accidental loss, not only tampering.

Where it runs

The check is one call: POST /api/v1/accountability/verify with a session ID. It's scoped to your organization from the authenticated request, so you can only verify your own sessions. The dashboard's Accountability page runs the same check and shows the result as a set of pass/fail marks.