Novo: Aprovação de agentes, red teaming e varredura de modelos. Veja como
MITRITY
Docs / Segurança / Tamper-Evident Audit Trail

Tamper-Evident Audit Trail

An audit log is evidence only if a reader can tell whether it has been changed. MITRITY makes that 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, and 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, and 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, and 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 — the organization's genesis hash for position 1.
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, 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, and 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 — a hash computed later over old content would prove nothing — and 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, as of a moment, the organization's chain ran from one position to another and ended in a particular hash. The platform seals one per organization about once a day, and 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. Its public half is available to every signed-in user at GET /api/v1/audit/checkpoints/public-key, together with a key_id; 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 — and states whether it covers the report's period.

Verifying a period

On the Audit log page, set a date range and 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; the default is the last 30 days. A verification recomputes every position in the span, checks every link, checks each checkpoint whose root falls in the span against the recomputed hashes and its signature against the public key, and extends the walk to the first checkpoint sealed after the period so 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

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 the signature covers, so you can 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, and 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), so 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. Starting from the genesis hash — SHA-256 of mitrity-audit-chain/v1:genesis: followed by the organization id — recompute each event's canonical record and hash in chain_seq order, checking 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

The events that record changes to people, their access and API keys (user.*, access.* and api_key.*, listed under Role Change History and API Keys) 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: they 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.

They stay on the trail. Verifying a period covers every event, these included, and the result names an event only by its position and ID. A SIEM destination receives them.

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.