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?'
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).
"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
Access Review & Recertification
Periodically review who has access to what, confirm it's still needed, and revoke what isn't. Kills the slow-drip accumulation of stale permissions that turn into audit findings.
Decision Table
Externalize complex branching logic — pricing tiers, eligibility rules, routing decisions — into a readable table that business users can maintain without a developer in the loop.
Regulatory Data Request Handler
Process CCPA, GDPR, and other data subject access requests (DSARs) within legally mandated timeframes. Manage intake, identity verification, data collection, review, and response.