Exporting the audit trail as evidence¶
The audit log lives in baseline.db on the verifier, hash-chained so that
nothing in it can be edited without breaking every later link. That is enough
to detect tampering on a verifier you still trust. It is not enough for an
auditor, and it is not enough after an incident: a chain that only exists on
the machine under investigation proves nothing about that machine.
audit-export writes the chain out as a signed, self-contained artifact that
verifies somewhere else, with no database, no key, no TPM and no daemon.
Writing a bundle¶
Like every op verb this is authorized by your kernel UID and recorded in the
chain — so the export itself appears as a record in the next export. Any
mapped role may run it: it reads records you could already read one at a time.
The daemon signs the bundle and hands back the bytes; your own client writes
the file, owner-only (mode 0600). The daemon never learns the path. That is
deliberate — a non-root daemon opening a path an operator chose would be an
arbitrary-write primitive running as the account that owns the baseline
database and the signing key.
The chain is verified before it is signed. A broken chain is refused rather than exported. A signature over a chain with a hole in it would be cryptographically impeccable and evidentially worthless: it would attest only that the verifier really did hold something broken.
Checking a bundle — anywhere but here¶
etminan-witness audit-export-verify \
--file audit-2026-08-28.json \
--expect-identity <64 hex chars>
Note what this command is not: it is not an op command. It opens no
database, loads no key and speaks to no daemon, so it runs on a laptop, on an
auditor's machine, on anything with the binary. Requiring the daemon here would
mean the evidence could only be checked on the machine that produced it, which
is not checking it at all.
Install etminan-witness on whatever machine that is — apt-get install
etminan-witness, or dnf install etminan-witness. It is one binary with no
services, no key and no configuration, and it needs neither verifier package.
The same command still exists as etminan-verifier audit-export-verify on the
verifier itself, which is useful for a quick look and proves nothing: evidence
checked only where it was produced has not been checked.
--expect-identity is required rather than read out of the bundle, and that
asymmetry is the whole point. A bundle checked against its own embedded key
would verify perfectly after an attacker re-signed a rewritten chain with a key
of their own. You have to bring the expected identity from somewhere else. It
is the daemon's public half, printed at every start:
Write it down once, out of band, and pin it forever.
What the reader checks, in this order:
- the bundle claims the identity you pinned;
- the signature over the whole payload verifies against it;
- every record's
entry_hashrecomputes from its own columns; seqruns contiguously from 1, and eachprev_hashlinks to its predecessor;- the head the bundle declares is the head its own records produce.
Step 4 is what makes a deletion visible. A hash chain alone does not catch
one: remove a run of records, relink the survivors, and every remaining link is
still intact. Requiring seq to run 1, 2, 3, … from genesis means a removed
record leaves a hole that cannot be closed without rewriting every later hash —
which step 2 then catches.
An empty chain is valid. A verifier that has never had a finding or an approval has nothing to export, and that is a boring state rather than a failure.
Where to keep it — and what this is not¶
The bundle is a periodic artifact, not a live control. It answers "here is the whole trail, provably intact as of this moment" for someone reading it later. It does not tell you today whether history was rewritten last night.
That second question is external anchoring of the audit head
(ETMINAN_AUDIT_WITNESS, Enterprise): small, continuous, published to a sink
the verifier cannot rewrite, and checked every cycle. The two are complements,
and neither substitutes for the other:
| audit-export | audit head witness | |
|---|---|---|
| what leaves the host | the whole chain | one (seq, hash) |
| how often | when you run it | every cycle |
| answers | "is this trail intact?" | "was history rewritten?" |
| needs | nothing but the file | a reachable sink |
For retention, treat the bundle like any other backup artifact: write it to the
path your backup already covers (see
Backup & disaster recovery) and keep it as long as your
retention policy demands. It contains the complete record of who did what on
this verifier, so keep it at least as protected as the verifier itself — the
0600 it is written with is a floor, not a policy.
Sending it by mail is not the same as retaining it
A mailbox is convenient and it is off-host, but it is not append-only: a mailbox can be deleted by whoever can delete it, and in air-gapped deployments the verifier suppresses mail entirely. If you want a signal by mail, send the head and the bundle's SHA-256 rather than the trail itself — that gives you a cheap second witness without putting every actor, action and resource on this verifier into an inbox.