Skip to content

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:

hash = SHA-256(id || org_id || actor_id || event_type || detail || created_at || prev_hash)

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.