Extension
Tamper-Evident Audit Records
A canonical byte form, a hash chain, and a verification procedure that make audit records checkable rather than merely stored
An audit log that can be edited after the fact records what someone was willing to keep, not what happened. The Tamper-Evident Audit Record Contract makes a log checkable: records carry a canonical byte form, each links to the one before it by hash, and a verifier can establish for a segment of the log that nothing in it was altered, reordered, or removed.
Authors
Scott Rhodes (@scottrhodes), Notboatanchor Labs LLC (@notboatanchor); Syed Maaz Ahmed (@MaazAhmed47), Interlock; Alfredo Metere (@enclawed), Enclawed, Inc.
An implementation running in compatibility mode does not use this at all.
The protected core
Every conforming record carries the same seven protected fields, whatever produced it:
| Field | Meaning |
|---|---|
event_id |
Identifier for this event |
occurred_at |
When it happened |
principal_id |
Who or what acted |
event_type |
What kind of event it was |
tool_name |
The tool involved, or null |
outcome |
allowed, denied, deferred, or error |
previous_hash |
Hash of the preceding record, or null to open the chain |
The core is deliberately emitter-neutral. Anything layer-specific belongs in extensions, a JSON
object keyed by registered type id — one entry per type — so a record from an admission-control
layer and one from a caller-governance layer share a shape a single verifier can check.
Registered profiles today are caller-governance, runtime-security, and admission-control,
the last being the profile ATSA emits.
What makes it tamper-evident
The type id is part of the preimage. An extension's type cannot be swapped for another without breaking the hash, so a record cannot be quietly re-labelled as something more innocuous.
Canonicalization is deterministic, and matches ATSA's: sorted keys, no bare numbers, bounded strings (8192 characters) and bounded nesting (64 levels). Two implementations that follow it produce identical bytes, which is what makes a hash comparable across systems at all.
Each record commits to its predecessor. event_hash is the SHA-256 of the canonical form of
the protected core plus extensions, and previous_hash carries the preceding record's
event_hash. Altering any record invalidates every record after it, so a log cannot be edited in
the middle without rewriting the tail and revealing that it was rewritten.
Append-only storage is attested, not assumed. The contract does not pretend the wire format can enforce how a store behaves. Instead, the guarantee is declared in an attestation manifest and checked there, which keeps the protocol honest about what it can and cannot prove.
Verification
A verifier checks, for a record and for a segment:
- the protected core is present and well-formed;
- every declared extension conforms to its registered type;
event_hashequals the hash of the canonical form;- each
previous_hashmatches the preceding record'sevent_hash; - required
event_typevalues are present for the sequence being attested.
A failure names which check failed, so "the log is invalid" is never the whole answer.
Implementing it
The reference verifier and its conformance vectors ship in this repository under
extensions/audit-record, vendored unmodified from
notboatanchor/audit-record-contract
(Apache-2.0, Copyright 2026 Notboatanchor Labs LLC). The full specification text is vendored
alongside it.
npm test
Thirty vectors run: twenty-six from the upstream suite, and four added here to supply rejecting cases that two requirements lacked — a verifier that always answered "ok" would have passed the known-answer test and the emission check. Every requirement now has both an accepting and a rejecting case, and a test fails if that stops being true.