Audit Hash Chain
The audit log uses a SHA-256 hash chain to provide tamper evidence. Each entry includes the hash of the previous entry, making it computationally infeasible to modify or delete past records without detection.
Schema
CREATE TABLE audit_log (
id BIGSERIAL PRIMARY KEY,
org_id UUID NOT NULL REFERENCES orgs(id),
actor_id UUID REFERENCES users(id),
event_type TEXT NOT NULL,
detail JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
prev_hash TEXT NOT NULL,
hash TEXT NOT NULL
);
The table uses an append-only policy — no UPDATE or DELETE is granted to the kvmfleet_app role.
Hash computation
Each row's hash is computed as:
The first entry in each organization's chain uses prev_hash = "genesis".
Verification
To verify integrity, walk the chain forward and recompute each hash:
for each row (ordered by id):
expected = SHA-256(row.id || row.org_id || ... || row.prev_hash)
if expected != row.hash:
CHAIN BROKEN at row.id
if row.prev_hash != previous_row.hash:
LINK BROKEN between row.id and previous_row.id
Via API
curl https://app.kvmfleet.io/v1/audit/verify?start=2026-01-01&end=2026-03-31 \
-H "Authorization: Bearer $TOKEN"
{
"verified": true,
"entries_checked": 1847,
"first_id": 1,
"last_id": 1847,
"chain_start": "2026-01-01T00:00:12Z",
"chain_end": "2026-03-31T23:58:44Z"
}
Via MCP
The verify_audit_integrity MCP tool runs the same verification and returns results to an AI assistant.
Event types
| Event | Logged when |
|---|---|
user.login |
Successful login |
user.login_failed |
Failed login attempt |
user.logout |
User logs out |
device.enrolled |
New device completes enrollment |
device.removed |
Device is deleted |
session.open |
Console session started |
session.close |
Console session ended |
team.invite |
Team member invited |
team.remove |
Team member removed |
alert.rule_created |
Alert rule created |
alert.fired |
Alert triggered |
report.generated |
Compliance report generated |
Why a hash chain?
A simple append-only table prevents casual tampering, but a database administrator could still modify rows. The hash chain adds a cryptographic guarantee: any modification to any row breaks the chain from that point forward. This is a requirement for NIS2 Article 21(a) — risk analysis and information system security policies.