Patterns
intermediategovernance

Immutable Audit Trail

Record every significant action to an append-only log that can be inspected but not altered. Essential for compliance, incident response, and answering 'who changed this and when?'

Views64
BPMN 2.0
On this page

Visual Flow

Rendering diagram…

When to Use This Pattern

Use an immutable audit trail whenever you need to answer:

  • Who did what, to what, and when?
  • What state was the system in at a specific point in time?
  • Who had access to which data, and when was it accessed?
  • Who approved this decision?

Compliance frameworks (SOX, HIPAA, SOC 2, GDPR) all require some version of this. Even without regulation, incident response is miserable without it.

How It Works

Every significant action — state change, permission grant, record view, approval — emits an audit event to an append-only store. The event includes: who (actor), what (action + target), when (timestamp), how (request ID, IP), and the before/after state where applicable.

"Immutable" means writes are only inserts. Updates and deletes aren't just discouraged — they're prevented by storage design (WORM storage, blockchain, or an append-only table with no UPDATE/DELETE grants).

Note

"Immutable" includes retention. A log that quietly rolls off after 90 days isn't an audit trail; it's a short-term buffer. Set retention to match your longest audit horizon.

Implementation Guide

Step 1: Define which actions are auditable

Not everything needs an audit event. Start with: authentication, privileged-data access, state changes to regulated records, approvals, permission changes, admin actions. Too many events = noise. Too few = gaps.

Step 2: Standardise the event shape

Every event should have: id, timestamp, actor, actor_type (user/service), action, target, target_type, outcome, before, after, request_id, ip, user_agent. Consistency makes queries sane.

Step 3: Write to the log before the action commits

If the audit write fails after the action succeeds, you have an action with no audit. Options: two-phase commit with the log, write audit first and the action second with compensation on failure, or use a transactional outbox to the log.

Step 4: Make the log append-only at the storage level

Grant the app INSERT-only on the table. No UPDATE, no DELETE. If the app is ever compromised, the attacker can add events but not erase them. Route reads through a separate role.

Step 5: Build the query surface

Admins and auditors need to search by actor, target, action, time range. A slow query here is a stalled investigation. Index appropriately; archive older events to a query-friendly cold store.

Tips & Best Practices

  • Include enough context to be self-explanatory. An event that reads "user:42 updated record:98" is useless six months later when you don't remember what record 98 was.
  • Capture the reason where you can. A field for "justification" on sensitive actions turns logs into useful narrative.
  • Separate audit from app logs. App logs roll. Audit logs don't. Different retention, different access, different tools.
  • Test restores. Can you recover the audit log after a disaster? If not, you don't really have one.
  • Don't log secrets. Never the password. Never the API key. Never the raw PII unless you've made a deliberate decision to.

Related patterns