Tamper-evident audit for out-of-band infrastructure access
A construction for audit logs that an external auditor can verify without trusting the platform that produced them — and the explicit boundaries of what that verification proves.
Abstract
Out-of-band management — the PiKVM units, BMCs, and IPMI controllers that sit beneath the operating system — is the highest-privilege and least-audited path into a server. The usual audit log for that path is a row in a database the operator fully controls, which is not evidence an auditor can rely on after a breach. This paper describes how KVM Fleet records out-of-band access events in a per-organisation SHA-256 hash chain that is append-only at the storage layer, anchored to external witnesses the customer controls, optionally signed by a key the customer never shares, and exported with RFC 6962 Merkle inclusion proofs. The integrity of the result is checked by an open-source command-line verifier that runs offline against an exported log, with no connection to the platform. We are deliberate about what each mechanism does and does not prove; Section 6 is a candid limitations section, and is the part of this document a sceptical reader should read first.
1.The problem
Every server has a management layer beneath its operating system: a baseboard management controller (BMC) reachable over Redfish or IPMI, or a discrete KVM-over-IP device such as a PiKVM. That layer can power the machine on and off, attach a virtual ISO and boot from it, read the screen and inject keystrokes before any OS login prompt, and reset the host to a known state. It is, by design, the most privileged path to the machine — and it is reachable when the operating system is down, locked, or compromised.
It is also, in most estates, the least audited path. SSH sessions land in a SIEM; console access through a BMC frequently does not. When out-of-band access is logged, the log is typically a set of rows in an application database that the operator can read, edit, and delete. For day-to-day operations that is adequate. For an auditor, or for a forensic investigator after an incident, it is not. A log the operator can silently rewrite is not evidence about the operator; it is an assertion by the operator. "Trust us, the log is complete and unaltered" is precisely the claim an audit exists to test.
The requirement, then, is narrow and specific: an audit record of out-of-band access whose integrity can be checked by someone who does not trust the party that produced it, using artifacts that party hands over, without granting that checker live access to the platform. The rest of this paper is the construction that meets that requirement, and an honest account of where its guarantees stop.
2.Threat model
We name the adversaries explicitly, because the design choices only make sense against them. The defended property is integrity of the historical record: an adversary must not be able to alter, reorder, insert, or delete a past event without the change being detectable by an independent verifier. (Confidentiality and availability are separate concerns and out of scope for this document.)
| Adversary | What they can attempt | Primary mitigation |
|---|---|---|
| Malicious insider (an operator with application-role credentials) | Edit or delete a row to hide an access event; reorder events to obscure a sequence. | DB-level immutability (UPDATE/DELETE revoked + trigger); hash chain makes any post-hoc change detectable. |
| Compromised platform operator (KVM Fleet itself, or an attacker who has taken over the platform) | Rewrite history with full database and application access, then recompute the chain to look consistent. | External-witness anchoring (independent copy of the chain head leaves the platform's control); customer-owned signing (signatures the platform cannot forge). |
| Attacker with direct database access (no application code path) | Issue raw SQL to alter or drop audit rows, bypassing application logic. | Hash chain (any altered row breaks every subsequent hash); external witnesses hold prior heads that a rewrite can no longer match. |
The hard case — and the one that dictates the architecture — is the compromised platform operator. A hash chain alone does not defend against an adversary who controls the whole database, because such an adversary can rewrite the events and recompute every downstream hash; the rewritten chain is internally consistent. Defeating that adversary requires something the operator cannot retroactively change: a copy of the chain's state that has already left the operator's control, or a signature the operator cannot produce.
This is exactly why external witnessing and customer-owned signing exist. They are not redundancy on top of the hash chain — they are the only mechanisms in this design that constrain an adversary who controls the platform's own storage. An organisation that runs the chain with no external witness and no customer signature has tamper-detection against insiders and DB-level attackers, but not against a fully compromised operator. Section 5 states this limit plainly.
We assume standard cryptographic hardness: SHA-256 is collision- and pre-image-resistant, and Ed25519 signatures are unforgeable without the private key, for the lifetime of the evidence. (The post-quantum erosion of that second assumption is addressed as a dated roadmap item in Section 6, not as a present capability.) We assume the customer's witness infrastructure and signing keys are under the customer's control and not co-located with the platform; an external witness hosted on the same operator the chain is defending against provides no independent observation.
3.The construction
3.1Hash chain
Each audit event records a fixed set of fields: org_id, occurred_at, actor_type, actor_id, action, target_type, target_id, result, and details. Every event carries a row_hash computed over a canonical serialization of those fields chained to the previous event's hash:
row_hash = SHA-256( prev_hash ‖ canonical(payload) ) # prev_hash = the previous event's row_hash, within the same org chain. # Genesis anchor (first event in a chain) = 32 zero bytes. # ‖ denotes byte concatenation.
The canonicalization is a deterministic, language-independent encoding so that an independent implementation can reproduce the exact bytes that were hashed. It is the event's payload fields serialized as:
canonical(payload) =
json.dumps(payload,
sort_keys=True,
separators=(",", ":"),
default=str)
# with ensure_ascii-style \uXXXX escaping of non-ASCII characters
Keys are sorted, there is no insignificant whitespace, and non-ASCII characters are escaped to \uXXXX form — so the byte string is fixed regardless of map ordering, locale, or language runtime. The open-source verifier reproduces this encoding byte-for-byte; if it could not, it could not recompute the chain.
The chaining property is the load-bearing one. Because each hash incorporates the previous hash, any modification, insertion, reordering, or deletion of an event invalidates that event's hash and every hash after it. A single altered byte anywhere in the history is detectable as a break at that point in the chain walk.
3.2Per-org chains and write serialization
Each organisation has its own independent chain. Writes within an org are serialized by a per-org advisory lock that wraps the read-previous-hash → compute → insert sequence as one critical section. Without this, two concurrent writers could both read the same prev_hash and produce a fork. The lock makes the chain strictly linear per org.
3.3Database-level immutability
The hash chain makes tampering detectable. Database-level immutability makes it blocked at the storage layer, so that detection is a backstop rather than the only line of defence. On the audit table:
UPDATE,DELETE, andTRUNCATEareREVOKEd from the application role.- A trigger raises an exception on any
UPDATEorDELETE. - The only sanctioned deletion is the retention sweep (Section 3.4), gated behind a transaction-scoped database flag that the sweep sets and nothing else does.
UPDATEis never permitted under any path.
The effect is that an application-role insider cannot edit or remove a past event at all — the storage layer refuses. The hash chain then catches the residual case of an adversary who reaches the database below the application role.
3.4Retention and truncation with anchor advance
Retention is configurable per organisation — a fixed window, or unbounded. Truncating old events is the one operation that must remove chain rows, and it is designed so the chain stays verifiable across the cut. When a sweep removes the oldest events:
- the chain anchor advances to the
row_hashof the last deleted row, and - an
audit.retention.sweptevent is written that chains off that advanced anchor.
An auditor verifies the pre-truncation segment against its original anchor and the post-truncation segment against the advanced anchor. The cut itself is recorded as an event, so the truncation is part of the auditable history rather than a silent gap.
3.5Customer-owned signing keys opt-in
A customer may bind the chain to a signing key the platform never possesses. The customer uploads an Ed25519 public key; the platform stores only the public half. The customer's own out-of-band signer — a reference implementation is provided — runs on the customer's infrastructure (or HSM), holds the private key there, signs the current chain head, and archives the signature. The platform embeds the key's SHA-256 fingerprint in its periodic anchor events so the binding is recorded in-chain.
The consequence: a platform-side attacker cannot forge a signature, because the private key is never on the platform. Any chain head the customer has signed is locked in — a later rewrite of history that does not reproduce that exact head will fail signature verification against the head the customer archived.
Signing is optional. The default external-anchoring mechanism (Section 3.6) is an HMAC-signed SIEM webhook. Customer-owned Ed25519 signing is the stronger opt-in for organisations that want the platform to be unable, even in principle, to produce a valid signature over a forged head.
3.6External-witness anchoring
Roughly every six hours, the platform publishes an audit.chain.anchor event carrying the current chain head, and fans it out to observers outside the platform's control:
- via an HMAC-signed webhook to the customer's SIEM, so the SIEM holds an out-of-band copy of the head at that time; and/or
- POSTed to customer-configured third-party witness HTTP endpoints, which may return a signed timestamp that the platform records.
This is what closes the compromised-operator threat. Once a chain head has been observed by an independent party — recorded in the customer's SIEM, or timestamped by a witness — the operator can no longer rewrite history prior to that head without contradicting the copy the witness already holds.
Anchors are published only for organisations that have configured at least one external witness — a SIEM webhook or a witness endpoint. This is deliberate. Without an independent external observer, an anchor row simply sits in the same database the operator controls and proves nothing about that operator; it could be rewritten alongside the events it anchors. External witnessing is the mechanism that closes the platform-operator tampering threat. An anchor with no witness is not a substitute for one.
3.7Merkle inclusion proofs (RFC 6962)
For minimum-disclosure evidence, events are grouped into per-UTC-day Merkle epochs using the RFC 6962 (Certificate Transparency) hashing rules, which domain-separate leaves from internal nodes:
leaf = SHA-256( 0x00 ‖ row_hash ) node = SHA-256( 0x01 ‖ left ‖ right )
The day-root is published. An auditor can then request an inclusion proof for specific events — the sibling hashes along the path from those leaves to the root. The proof demonstrates that exactly those events belong to the published day-root without disclosing the other events of that day. This matters when a log contains access events for systems or tenants the auditor is not entitled to see: the auditor can confirm the events in scope are genuine members of the committed set, and learn nothing about the rest.
An inclusion proof proves two things: that the named events are members of the published day-root, and that each named event's row_hash is intact. It does not prove chain continuity — that no event was deleted between the disclosed ones — because a filtered export omits the intermediate rows the chain walk would need. Continuity is proven by a full chain walk over an unfiltered export (Section 4). Inclusion proofs are for minimum-disclosure spot-checks; the full walk is for completeness.
| Mechanism | Status | Note |
|---|---|---|
| SHA-256 per-org hash chain | Shipped | Append-only, canonical encoding |
| DB-level immutability (revoke + trigger) | Shipped | UPDATE/DELETE blocked at storage |
| Retention sweep with anchor advance | Shipped | Verifiable across truncation |
| External-witness anchoring (SIEM / endpoint) | Shipped | Required for anchors to be published |
| Customer-owned Ed25519 signing | Opt-in | Platform holds public key only |
| RFC 6962 Merkle inclusion proofs | Shipped | Per-UTC-day epochs |
| Offline OSS verifier (CLI + WASM) | Shipped | BSL 1.1, runs with no network |
| Hybrid Ed25519 + ML-DSA-65 anchors | Roadmap | Targeted 2027-03-31 |
| KVM-Fleet-operated witness network | Roadmap | Year 3–5 (Layer-3) |
4.Independent verification
4.1The open-source verifier kvmfleet-verify
kvmfleet-verify is a Go command-line tool published at github.com/KVMFleet/audit-verify under the Business Source License 1.1, which converts to Apache 2.0 four years after each release. It runs offline: no network access, no connection to the platform. An auditor builds it from public source, runs it against an exported evidence pack, and checks the result themselves. It performs four checks:
- the full hash-chain walk — recompute every
row_hashfrom the genesis anchor and confirm each links to the next; - customer Ed25519 signed-anchor signatures — verify the customer's signatures and confirm the signed head actually appears in the walk;
- external-witness Ed25519 signatures over
chain_head ‖ timestamp; and - Merkle inclusion proofs — reconstruct the day-root from the supplied proof and confirm it matches the published root.
# Walk the full chain offline, from the org's anchor: $ kvmfleet-verify --input ./export.ndjson --anchor 1a2b…64hex # Also verify your own Ed25519 signature over the chain head: $ kvmfleet-verify --input ./export.ndjson \ --signed-anchors ./anchors.txt --customer-pubkey 9f3c…64hex # Verify an auditor's minimum-disclosure inclusion proof: $ kvmfleet-verify --filtered ./subset.ndjson --proof ./subset.proof exit 0 # every check passed: chain verified exit 1 # any break or mismatch: chain rejected
The exit code is the whole result: 0 means verified, 1 means at least one check failed. Because the tool is offline and open-source, the auditor's trust anchor is the source code they can read and the math they can re-run — not an assurance from KVM Fleet.
4.2In-browser verifier (WebAssembly)
The same verifier is compiled to WebAssembly and runs entirely in the browser at kvmfleet.io/security.html. An evaluator can paste a log, flip a single byte, and watch the chain break — a fast way to build intuition for what the construction does.
KVM Fleet serves that page, so the WASM demo is a convenience and education tool, not the authoritative verification. The authoritative independent check is the open-source CLI, built from source and run offline by the auditor. If the two ever disagreed, the offline CLI built from public source is the one to trust.
4.3Minimum-disclosure proofs for auditors
For scoped engagements, the auditor does not need the entire log. Using the Merkle inclusion proofs of Section 3.7, the customer can hand over only the events in scope plus their inclusion proofs against the published day-roots. The verifier confirms those events are genuine members of the committed set, while the remaining events of each day stay undisclosed. Where the engagement requires proof that nothing was dropped, the auditor runs the full chain walk over an unfiltered export instead — the two modes serve different questions, as Section 5 makes explicit.
5.What this does and does not prove
This section is deliberately blunt. The credibility of everything above depends on being exact about where the guarantees stop. None of the following is a footnote; each is a real boundary an auditor should hold us to.
| This is true | This is not |
|---|---|
| The hash chain detects any alteration, insertion, reordering, or deletion within an exported segment. | A hash chain alone does not stop an adversary who controls the whole database from rewriting events and recomputing every downstream hash. That case is addressed by external witnessing and customer signing — not by the chain itself. |
| External-witness anchoring closes the compromised-operator threat by putting a chain head outside the operator's control. | If an organisation configures no external witness, no anchors are published and the anchor concept provides nothing against the operator — an in-DB anchor sits in storage the operator controls. No witness, no operator-level guarantee. |
| A Merkle inclusion proof proves the named events belong to the published day-root and that their row hashes are intact. | An inclusion proof does not prove continuity — that nothing was deleted between the disclosed events — because the filtered export omits intermediate rows. Continuity needs a full chain walk over an unfiltered export. |
| The in-browser WASM demo runs the same verification logic locally in the evaluator's browser. | It is not the independent check, because KVM Fleet serves the page. The authoritative check is the open-source CLI built from source and run offline. |
| Customer-owned Ed25519 signing makes platform-side forgery infeasible and locks in any head the customer has signed. | Signing is optional and off by default. An organisation that does not enable it relies on DB-level immutability, the hash chain, and (if configured) external witnessing — but not on a signature the platform cannot produce. |
| Ed25519 signatures and SHA-256 are sound under standard cryptographic assumptions today. | Nothing in the shipped system is post-quantum. Post-quantum hybrid anchoring is a dated roadmap commitment (Section 6), not a current capability. |
In short: the hash chain and DB-level immutability give you tamper-evidence and storage-layer immutability against insiders and direct-database attackers out of the box. Defending against a fully compromised platform operator additionally requires that the organisation actually configure an external witness, and — for the strongest position — enable customer-owned signing. Those are operational choices the customer makes, and we would rather state that plainly than imply the operator threat is closed by default.
6.Roadmap
The items below are not yet shipped. They are forward commitments, stated in the future tense and dated where a date exists. Nothing in this section should be read as a description of current behaviour.
6.1Post-quantum hybrid anchors — targeted 2027-03-31
KVM Fleet will introduce hybrid Ed25519 + ML-DSA-65 chain anchors, with a target date of 2027-03-31. The intent is that anchors will carry both a classical and a lattice-based signature, so that the integrity of historical evidence survives a future break of the classical scheme. As of this document, nothing post-quantum is shipped; every signing and anchoring path described in Sections 3 and 4 is classical Ed25519.
6.2Operated witness network — Year 3–5
A KVM-Fleet-operated public witness network — an independent verification service that third parties could anchor against — is future Layer-3 work, scoped to Year 3–5. Today the platform can only call witnesses the customer configures; it does not operate a witness service of its own. Until that work ships, every external witness in this design is one the customer points us at and controls.
7.References & EU posture
KVM Fleet is hosted at Hetzner FSN1 in Falkenstein, Germany, operated by a Maltese legal entity, under a GDPR-aligned Data Processing Agreement that incorporates the EU Standard Contractual Clauses (Module 2).
- github.com/KVMFleet/audit-verify —
kvmfleet-verify, the offline open-source verifier (Go, BSL 1.1 → Apache 2.0 after four years). - kvmfleet.io/security.html — the in-browser (WebAssembly) verifier and live tamper demo.
- kvmfleet.io/compliance.html — compliance evidence, framework reports, and the verifier in context.
- RFC 6962 — Certificate Transparency; the Merkle tree leaf/node hashing construction used for inclusion proofs.
- RFC 8032 — Edwards-Curve Digital Signature Algorithm (EdDSA); the Ed25519 signatures used for customer and witness signing.