What a tamper-evident audit trail actually buys you
A sealed trail does not stop anyone changing a record. It makes a change visible — to whoever holds a copy the person changing it could not reach. This is what BROKA seals, how to check it with a tool that is not BROKA, and the places where the proof stops.
A tamper-evident trail buys you one thing: an alteration, a deletion, an insertion or a reordering of its records can be detected. It does not prevent any of them, and on its own, inside the database it describes, it proves that only against people who cannot write that database. Everything else in this article is about turning that narrow guarantee into evidence you can hand to someone — and about saying exactly where it ends, because an audit artefact that overstates itself is the one that gets relied on.
The questions, and what answers them
| The question | What answers it | Where the answer stops |
|---|---|---|
| Was this record changed? | Verify integrity, or the open specification's own verifier run over an exported range | Not against someone who can write the database — unless you hold a checkpoint or a forwarded copy they could not reach |
| Is anything missing? | A record gone without a retention record to account for it breaks the chain | Not at the newest end: a chain cut short is internally consistent |
| Who did it? | The account, sealed by id, with its address, session and user agent | The address is the one BROKA was given; behind a proxy it is the client's only if BROKA trusts that proxy |
| Why? | The reason, exactly as the operator typed it | A reason is a statement, not an approval; nothing checks that it is true |
| Who tried, and was refused? | Every refusal, recorded under the refused operation's own name, with what the request asked for | What a refusal cannot know — what a broker that failed a write had already done — it says it does not know |
| What did people read? | The privileged reads, in Commercial — and every read of masked contents shown unmasked, whatever the read setting | Community records every change and every refusal, and no reads |
| What did an AI assistant do? | In Commercial, every call through the MCP server, marked with that channel, the tool, the client and the token's prefix | It names the account whose token the assistant used, not what the assistant was asked |
1 — Every action is one event
Every record in the trail is one OpenAuditModel 1.0 event — an open, published format for audit
events — and the screen reads its rows off those events, so the entry you click and the file an auditor
checks are the same document. Event names follow the specification's grammar, broker.topic.delete or
broker.offset.reset, and never carry a product name: the platform is a field of the event. A refused
operation is recorded under the same name as a successful one, so "who tried to delete this topic" is
one filter, not a search through generic failure entries.
An event says when, who — the account by id, the address, the session, the user agent, and whether the account held administrative access at that moment — what, on which resource and connection, in which environment, and how it ended: Success, Denied (a permission was missing), Blocked (a policy stopped a request the person was entitled to make) or Failed. Where it changed something it records both sides where that is safe, and a property that holds a credential by name only. It never carries a password, a key, a token, a message payload or the filter someone searched with.
The record and the change commit together. For a change to BROKA itself — a user, a role, a connection, a setting — if the record cannot be written the change does not happen, and there is no list of actions exempt from that rule. A change on a broker cannot be taken back once the broker has made it, so its record is written when the broker answers, and a client hanging up cannot cancel it. A write the broker never answered is recorded as a failure marked outcome unknown, because the broker may have carried it out.
2 — Sealed into one chain
Each event is sealed into a single hash chain per installation: the event, in its RFC 8785 canonical JSON form, hashed with SHA-256 together with the previous link's hash. Canonical form matters because it is defined by a specification, not by a serializer's settings, so the same event hashes the same way in BROKA and in any other implementation.
The seal is kept in a table of its own, so the audit rows themselves are never updated. Sealing runs behind live traffic — every 30 seconds by default — and the newest events are therefore not sealed yet. The console says so rather than implying coverage it does not have: an entry still waiting reads Event (not sealed yet), and verification counts those events apart from its verdict.
3 — Check it, with BROKA and without it
Verify integrity, on the audit log, walks the chain over the dates chosen and reports the first break it finds, with its kind: a link that no longer follows its neighbour, a record missing without a retention record to account for it, content that no longer hashes to what was sealed, or a link sealed under an algorithm it cannot recompute. It reports the first break rather than a count, because past one break every later link mismatches by construction. Beside the verdict it says how many links it checked, how many records retention removed, how many are too recent to have been sealed, and how many timestamps run backwards — reported apart, because a clock correction causes them and that is not tampering.
A check run by the system under examination is a weak kind of evidence, so the trail leaves BROKA in a
form someone else can check. Export ▸ Evidence pack (zip), for a range with both dates set, holds
the entries as CSV views, the sealed events in an events folder — events/000001.ndjson,
events/000002.ndjson and on, each at most 8 MiB, so the reference tool can read every file whole —
checkpoint.json — the chain's head at the end of the range — the integrity verdict, the retention
state and a manifest with a SHA-256 of every other file and the limits of what the pack can claim.
Date range as NDJSON exports the sealed events too, exactly as the chain holds them: it takes the
dates and nothing else, because the file is the chain for those dates.
The specification publishes a reference command-line tool, @openauditmodel/cli, and BROKA does not
need to be running for it to work:
openauditmodel verify-chain events/
openauditmodel verify-checkpoint --checkpoint checkpoint.json events/
Point both at the pack's events folder, which the tool reads as one chain, file by file — the whole
range, unnarrowed. The NDJSON export can be checked the same way. Neither is ever narrowed by another
filter: a chain with events left out does not verify, and every missing event would read as a
deletion; a narrowed view of the trail is the CSV. A range whose oldest events retention has already
removed starts mid-chain; the reference tool reports it as a segment and passes it.
The same tool's check-profile holds events to the specification's profiles. Over BROKA's events it reports three
things BROKA states rather than meets: there is no multi-factor sign-in, so the two rules that ask for one on a
privileged grant fail; a request refused before it ran that gave no reason is recorded without one; and a failed
operation names, under dev.broka.unstated, each fact it cannot state.
The checkpoint is the part that does most of the work, and only once it leaves the building. Kept somewhere the installation's operators cannot write — with the auditor, in a ticket, in another system — it fixes a head the chain had at a moment. A chain rewritten after that moment, or cut short, no longer arrives at it.
4 — What a reason records
A destructive operation — a delete, a purge, a trim, an offset reset — asks for a reason in every environment, and so do a few others whose consequences warrant it, such as a role assignment or a permission grant. A guarded environment asks for one on every write. The sentence the operator types is recorded on the event exactly as sent. A request that arrives without one is refused before anything runs, and the refusal is recorded as blocked — with no reason, because none was given, and nothing is invented in its place. A run of refusals like that is itself worth reading: the console asks for the reason before it sends, so they come almost entirely from scripts and API callers.
A reason is a statement, and the trail records it as one. It is not an approval: BROKA has no approval step, so a reason was not checked by a second person, and nothing checks that it is true. Where a write has no confirmation of its own, the console's A reason is required window can keep the same sentence for the next ten minutes or ten operations in that environment, when the operator ticks its box — so several events can carry one reason that was typed once.
5 — Retention and forwarding
The trail keeps at most six months — 180 days, which is also the default — and retention cannot be switched off. The window can be shortened in Settings ▸ Security; that needs the security permission and a reason, and it is recorded. What retention removes is accounted for: the chain keeps its links across the gap and a sealed destruction record states the cutoff and the count, so an absence that policy caused is not mistaken for one that somebody caused.
Anything older than six months exists only in a copy the trail was forwarded to. What leaves is the sealed event, unchanged, in chain order, so the receiver can run the same chain check over what arrived. A file or an HTTP endpoint is set in the deployment environment, in either edition. In Commercial, a Kafka or RabbitMQ connection you have already registered can be chosen in Settings ▸ Security instead; saving it and stopping it need a reason and are recorded, and while the Commercial licence withholds commercial writes it stops, and resumes where it stopped. Only one destination is used at a time, and delivery is at least once, so a receiver keys on the sequence.
A record that was never forwarded is still deleted at 180 days; BROKA warns seven days before. The topic or queue that receives the trail has to keep what it receives for as long as you need the evidence — its own retention is now the trail's.
6 — What it does not prove
Stated plainly, because these are the sentences an auditor will test:
- Nothing against someone who can write the database. The chain is unkeyed and lives in the same database as the records, so anyone with write access to it — including whoever holds the credentials BROKA's own services use — can change a record and reseal every link after it, and the chain will verify. The seal is a hash chain, not a signature. Only a copy they could not reach shows the difference: a checkpoint kept elsewhere, or the forwarded trail.
- That nothing was cut from the newest end. A truncated chain is internally consistent; nothing inside it records how long it should be. A checkpoint from before the cut is what shows it.
- The newest moments. Events not yet sealed are outside every verdict until the next pass.
- Anything done on a broker without BROKA. The trail records what passes through the console. A topic deleted with the broker's own tools, or by an application, is not in it.
- That a reason is true, or that anyone approved it.
- What a Community installation read. Community records every change and every refusal and no reads; in Commercial, the default records the privileged reads, not every read.
- Who someone is, beyond the account. An event names the account that acted — signed in, or through one of its API tokens, an AI assistant's included — not the person at the keyboard.
A sealed trail is evidence. It is not, by itself, compliance with any regulation, and nothing BROKA writes says it is.
Who can check it
Verifying the trail, exporting it and producing an evidence pack need the audit permission, which the built-in Auditor role holds without any permission to change anything. Each export is itself recorded, with how many records it held and which filters narrowed it. In Commercial, so is each page of the trail someone reads on the screen — which filters narrowed it, never what they were set to — so the trail also says who looked at it. In Commercial the evidence pack also carries the access report — who holds which permission, as of the moment it was produced. The audit page describes the trail from the product's side.
What this article does not cover
- The access review itself. Reading the trail against who holds which permission is a procedure of its own, described under Audit review.
- Alerting. Turning a pattern in the trail into a page someone receives is Alerts, with its own rules and channels.
Try it yourself
The audit-trail lab runs BROKA from the install recipe with the trail forwarded to files and a Kafka broker to change things on, with scripts that check an evidence pack and the forwarded copy using the reference CLI and then edit one record in the database, so you can watch the three checks stop agreeing.
Applies to BROKA 1.0 · Community and Commercial. Read recording, the evidence pack's access report and forwarding to Kafka or RabbitMQ are Commercial.




