Audit Trails & Traceability
An audit trail is a data structure with legal weight. It is an append-only, attributable record of what changed, who changed it, and when. This is the implementation view: how to model and write that trail correctly in the data tier. It complements the compliance view in Auditability & Evidence.
Traceability means that for any record or decision you can rebuild its history: the sequence of changes, the actor behind each one, and enough context to understand why. Building that well is a data-modelling problem. You capture the right events with the right fields, write them in the same transaction as the action, and store them so they cannot be quietly altered.
Get the mechanism right and audit becomes a reliable by-product of normal operation, not a scramble when an investigator asks. Get it wrong, with entries that can be edited, actions that commit without their audit row, or timestamps you cannot trust, and the trail is worse than useless. It looks authoritative while being incomplete.
Capture the right record
- AlwaysFor every auditable event, record who (the authenticated actor), what (the action, and the before and after where it matters), and when (a trustworthy UTC timestamp).
- DoAudit security and compliance events: regulated decisions, permission and risk-band changes, access to sensitive data, and destructive operations.
- DoScope each audit record to its tenant, and store enough context that it makes sense on its own, without a person to interpret it.
- ConsiderA consistent event shape (entity, action, actor, timestamp, before and after, correlation id) so the trail is easy to query and joins to operational data.
- Do notRebuild audit from general application logs that rotate or get edited. They are not designed to be evidence.
Make it trustworthy
- DoWrite audit records append-only: inserted once, never updated or deleted by application code.
- DoWrite the audit entry in the same transaction as the action it records, so the action cannot succeed without its trail.
- DoStore the trail where it resists tampering (write-once, restricted permissions, or hash-chaining) and keep it for the full regulatory period.
- ConsiderHash-chaining or sequence numbering, so any gap or change in the trail can be detected and is not silent.
- Do notGive the application (or an attacker who compromises it) the ability to rewrite history. Keep audit write and read permissions separate from ordinary data access.
- NeverPerform a destructive or risk-changing operation without an immutable audit record, or edit or delete statutory AML or KYC evidence.
db.Execute("UPDATE Audit SET Detail=@d WHERE Id=@id"); // editable
// ...and written in a separate call that can fail independently of the action
An editable audit row can drift out of sync with the action it claims to record. Under scrutiny it proves nothing, and may prove the wrong thing.
using var tx = conn.BeginTransaction();
conn.Execute("UPDATE Customers SET RiskBand=@to WHERE Id=@id AND TenantId=@t", p, tx);
conn.Execute(@"INSERT INTO AuditLog(Entity,Action,ActorId,FromValue,ToValue,OccurredUtc)
VALUES('Customer','RiskBand',@actor,@from,@to,@now)", p, tx);
tx.Commit();
Insert-only, committed atomically with the change, attributed and timestamped. The record holds up because it cannot have happened any other way.
Self-review checklist
- AskDoes this action record who, what, when, and (where it matters) the before and after?
- AskIs the audit entry written in the same transaction as the action? Can the action ever commit without it?
- AskCan the audit record be updated or deleted by application code, or by an attacker who got in?
- AskWould a gap or change in the trail be detectable?