← All notes

Audit Management

Append-only is what makes a trail a trail

An audit log with an UPDATE statement anywhere near it is a report. The value comes entirely from the fact that nothing can be changed after the fact.

  • Invexa Technologies
  • 2 min read

Most systems have an audit table. Far fewer have an audit trail. The difference is whether anything in the system is capable of modifying a row after it is written.

The property being claimed

When you show an auditor a log, the implicit claim is: this is what happened, and it has not been edited since. If the application’s database user holds UPDATE and DELETE on that table, the claim is unsupported — not because anyone did edit it, but because nobody can demonstrate they did not.

That is the entire game. Not whether it was tampered with. Whether tampering is detectable.

Enforce it below the application

Application-level discipline is not a control, because the next developer does not know about it. Push it into the database:

REVOKE UPDATE, DELETE ON audit_entries FROM app_user;

Plus a trigger that raises on either, so a future privilege grant does not silently reopen the door. Two statements, applied once.

If the platform supports it, write the artefacts to object storage with an immutability policy and a retention period. That moves the guarantee outside the database entirely, which is stronger again.

Chain the entries

Immutability stops edits. Chaining detects deletions and reordering:

audit_entries
  id, occurred_at, actor, action, target, detail
  prev_hash
  entry_hash      -- hash(prev_hash + this row's fields)

Removing an entry breaks every hash after it. A daily job verifies the chain and alarms on a break. Cheap to compute, and it turns “we think the log is complete” into something you can run.

Log the decision, not just the mutation

A row-level change log tells you a value went from A to B. It does not tell you who approved it or why, which is what every finding actually asks.

Capture the intent alongside: the request, the approver, the reason, the ticket. status: pending → approved, by: ..., because: ... is evidence. status_id: 2 → 3 is a diff.

What it costs

Two grants, a trigger, a hash column, and a verification job. Perhaps a day. It is the difference between handing over a log and having it accepted.

Next step

Have a project in mind?

A 30-minute call is usually enough to know whether we are the right team for it. If we are not, we will say so.

Start a project

Replies within one working day