New: Agent approvals, red teaming and model scanning. See how
MITRITY
Docs / Security / Tamper-Evident Audit Trail

Tamper-Evident Audit Trail

An audit log is evidence only if a reader can tell whether it has been changed. The tamper-evident audit trail gives you and your auditors that check, and MITRITY makes it a property of the trail itself, rather than of the people who run it:

  • every audit event carries a hash of its own content and the hash of the event recorded before it;
  • about once a day, the platform signs a statement of where the chain stands.

Changing an event, removing one or inserting one out of order breaks a link that the next event still carries. Rewriting the whole chain consistently still cannot produce a signature the platform did not make.

You can ask the platform to verify any period, from the audit log page or the API. An auditor can recompute the trail with nothing but the exported events, the checkpoints and a public key.

How the chain works

Each organization has one chain. Every audit event written since the feature went live carries three fields:

FieldMeaning
chain_seqThe event's position on the chain: 1 for the first, then contiguous.
prev_hashThe row_hash of the event at the position before it. For position 1, it is the organization's genesis hash.
row_hashSHA-256 over the event's canonical record, which includes chain_seq and prev_hash.

The canonical record is the event's stored fields rendered as RFC 8785 canonical JSON: keys sorted, no whitespace, and one spelling for every number and string. That is what makes the hash reproducible. The metadata you sent and the metadata the database stored may differ in whitespace and key order, and both hash the same.

Events are chained in the order they were recorded, not the order they occurred. A connector that imports last week's activity today appends it at the head. A verification therefore walks positions, not timestamps. It covers every position between the earliest and the latest event of the period you ask about.

Events written before the chain went live carry none of the three fields. They are never chained retroactively, because a hash computed later over old content would prove nothing. A verification over a period that includes them says so.

What retention leaves behind

Your plan's retention window and the archival job remove old events. A removed event leaves a tombstone: its position and its two hashes, never its content. The next event's prev_hash still resolves, so the chain links through the gap, and a verification reports those positions as retired rather than missing. A position with neither an event nor a tombstone is missing, and missing is broken.

Checkpoints

A checkpoint is a signed statement that, at a given moment, the organization's chain ran from one position to another and ended in a particular hash. The platform seals one checkpoint per organization about once a day. Each checkpoint also records the previous checkpoint's root, so the checkpoints form a chain of their own.

Checkpoints are signed with an Ed25519 key that lives outside the database. Every signed-in user can fetch its public half, together with a key_id, at GET /api/v1/audit/checkpoints/public-key. Every checkpoint records the key_id that signed it, so a key rotation is visible per checkpoint instead of invalidating history.

Every compliance report's data-integrity section cites the latest checkpoint: when it was sealed, the positions it covers, its root hash and a prefix of its signature. The section also states whether the checkpoint covers the report's period.

Verifying a period

To verify a period from the dashboard:

  1. On the Audit log page, set a date range.
  2. Choose Verify period.

Managers and owners can run a verification. The result is itself recorded on the trail, as an audit.chain.verified event with who ran it and what it found.

The same check is available as POST /api/v1/audit/verify:

curl -X POST https://api.mitrity.com/api/v1/audit/verify \
  -H "Authorization: Bearer $MITRITY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"from": "2026-09-01T00:00:00Z", "to": "2026-09-21T00:00:00Z"}'
{
  "status": "valid",
  "period_start": "2026-09-01T00:00:00Z",
  "period_end": "2026-09-21T00:00:00Z",
  "chain_started_at": "2026-08-14T09:12:41Z",
  "events_in_period": 940,
  "unchained_events": 0,
  "first_seq": 1,
  "last_seq": 940,
  "events_verified": 931,
  "events_failed": 0,
  "retired_events": 9,
  "missing_events": 0,
  "checkpoints_checked": 2,
  "checkpoints_failed": 0,
  "truncated": false,
  "notes": []
}

Both bounds are optional, and the default is the last 30 days. A verification:

  • recomputes every position in the span and checks every link;
  • checks each checkpoint whose root falls in the span against the recomputed hashes, and its signature against the public key;
  • extends the walk to the first checkpoint sealed after the period, so that the events are anchored to a signature.
StatusMeaning
validEvery event recomputed and linked, every checkpoint matched and verified, and the period is anchored to a checkpoint.
brokenAt least one event, link, position or checkpoint did not match. first_divergent_seq, first_divergent_event_id and failure locate the first one; the counts say how much is wrong.
partialNothing failed, but the result is not the full guarantee: events predate the chain, the walk stopped at the 250,000-position cap, no checkpoint anchors the period yet, or a checkpoint was signed by a key the deployment no longer holds. notes says which.

A partial result on a fresh organization is expected: the first checkpoint is sealed about a day after the first chained event. A broken result is something to act on. The response tells you where to look.

Listing checkpoints

To list the checkpoints sealed in a period:

curl "https://api.mitrity.com/api/v1/audit/checkpoints?from=2026-09-01T00:00:00Z&to=2026-10-01T00:00:00Z" \
  -H "Authorization: Bearer $MITRITY_API_KEY"

Each checkpoint carries first_seq, last_seq, event_count, root_hash, prev_root_hash, key_id, signature, and the exact statement that the signature covers. You can therefore verify the signature over the bytes that were signed, rather than reassembling them. Managers and owners can list checkpoints.

Verifying offline

An auditor who does not want to trust the platform's own verifier needs three things:

  • an export of the events (the list endpoint carries chain_seq, prev_hash and row_hash, and every field of the canonical record, source included);
  • the checkpoints;
  • the public key.

Make the export as an Owner or a Manager. Changes to people, their access and API keys are shown to Owners and Managers only (see Who sees changes to people and API keys). An export made by a Member or a Viewer has gaps where those events sit.

The procedure:

  1. For each checkpoint, verify signature over statement with the public key (Ed25519).
  2. Start from the genesis hash: SHA-256 of mitrity-audit-chain/v1:genesis: followed by the organization id. In chain_seq order, recompute each event's canonical record and hash. Check that each event's prev_hash equals the previous hash, and that its row_hash equals the recomputed one.
  3. Confirm that the hash at each checkpoint's last_seq equals its root_hash.

The canonical record is the RFC 8785 rendering of an object with these keys: v (mitrity-audit-chain/v1), seq, prev_hash, id, tenant_id, environment_id, event_type, source, agent_id, user_id, policy_id, edge_node_id, action, mission_scope, decision, blocked_by, surface, traffic_type, enforcement_mode, behavioral_hash, drift_score, response_time_ms, metadata, occurred_at and created_at.

  • Absent values are null.
  • Timestamps are RFC 3339 in UTC, with six fractional digits.
  • metadata is the stored JSON value.

Any conforming RFC 8785 library produces the same bytes.

Who sees changes to people and API keys

Some events record changes to people, their access and API keys: user.*, access.* and api_key.*, listed under Role Change History and API Keys. These events name people by email address and role.

  • Owners and Managers see them.
  • Members and Viewers, Academy learners included, see the rest of the audit log without them. These events are left out of the list, its total and the audit summaries, and opening one by ID answers 404 Not Found.
  • Reports never include them.

These events stay on the trail. Verifying a period covers every event, these included, and the result names an event only by its position and ID. They are not forwarded to SIEM destinations.

What this does and does not protect against

SituationResult
An event's content is edited in place.Its row_hash no longer matches: broken at that event.
An event is deleted outside retention.Neither event nor tombstone at that position: broken.
Events are rewritten and every hash recomputed consistently.The links verify; the checkpoint root or signature does not: broken. Before the first checkpoint over those events: partial, stated.
A checkpoint is edited.Its signature no longer verifies: broken.
Events written before the chain went live are altered.Not detectable; the verification reports how many such events the period contains.

The signing key is held in the platform's secret store and read once at start-up. Rotating it does not affect earlier checkpoints.