Audit log
When someone changes something that matters, Sentinel appends an entry to the audit log: who did it (an API key, sentinel-admin or bootstrap), what they did, to which record, and the details. Regulators and auditors rely on it, so it's built to make tampering obvious.
What gets recorded
| Action | When |
|---|---|
alert.updated | An alert's status or assignee changes |
alert.note_added | A note is added to an alert |
alert.closure_requested, alert.closure_approved, alert.closure_rejected | Each step of closing an alert |
alert.closed | A screening alert closes by itself because every match was cleared and approved |
screening.match_resolved | Someone records a decision about a screening match |
screening.resolution_approved, screening.resolution_rejected | A second person approves a cleared match, or anyone sends it back |
rule.created, rule.activated | A rule draft is created or approved |
rule.seeded | sentinel-admin rules seed installs the built-in rules |
api_key.created, api_key.revoked | A key is created or revoked |
api_key.bootstrapped | The bootstrap key is stored on a fresh install |
Each change and its audit entry are written in the same database transaction, so one never exists without the other.
Events, decisions and screening results aren't audit entries. They're kept in their own tables, which are only ever added to by the API, and each decision stores the rule versions and feature values behind it.
Each entry has:
| Field | Meaning |
|---|---|
seq | Position in the log |
actor | The key ID, or sentinel-admin or bootstrap |
action | What happened, for example alert.updated |
target_type, target_id | The record it happened to |
data | The details of the change, as JSON |
created_at | When it was written |
prev_hash | The previous entry's hash |
hash | This entry's hash |
Append-only
Database triggers refuse any UPDATE, DELETE or TRUNCATE on the audit table. Entries can only be added.
Hash-chained
Each entry's hash covers the previous entry's hash. Change one entry and every later hash stops matching.
| Entry | prev_hash | hash |
|---|---|---|
| n − 1 | … | 0a7e19c2… |
| n | 0a7e19c2… | 9f2c41b8… |
| n + 1 | 9f2c41b8… | e2c97f45… |
The details:
- Each hash is SHA-256 over the previous hash and every field of the entry.
- Each field is length-prefixed, so values can't be shifted between fields to forge a match.
- Chains are per organisation and written under a lock, so entries never interleave.
Verifying the chain
sentinel-admin audit verify -org default
It walks the organisation's chain from the start. If every entry matches it prints how many it checked. Otherwise it reports the first entry that doesn't match and exits with an error.
Not built yet
There's no API or dashboard screen for reading the audit log, and no signed export for auditors. For now, read the audit_log table directly and run audit verify.