Back to journal
SaaS8 min readSeptember 21, 2026

Audit Logs That Survive an Enterprise Security Review

How to design a SaaS audit trail buyers will accept: what to record, append-only storage, tamper evidence, retention, tenant-scoped access, and the questions reviewers ask.

#SaaS audit log design#CodeAustral
Audit Logs That Survive an Enterprise Security Review

Short answer: an audit log is a product surface, not a debug table. Enterprise reviewers want to know what happened, who did it, when, from where, and whether the record can be altered or quietly deleted. If your answer to "can an administrator modify the audit trail" is anything other than a confident no, that finding will appear in the security review — and it will cost you more to fix under deal pressure than it would have cost to design it correctly.

A long row of blank archive boxes on steel shelving in a records room

An audit trail is only credible if it is complete, ordered and tamper-evident.

Start from the questions reviewers ask

Audit design gets easier when you work backwards from the review questionnaire. These are the questions that decide whether your trail is accepted, and each one maps to a design decision:

  • Can we see every authentication and authorization event for our tenant, including failures?
  • Can we see who changed permissions, and what the permissions were before and after?
  • Can we prove the records have not been modified since they were written?
  • Can we export the trail for a date range, in a machine-readable format, without asking your support team?
  • What happens to the trail when a user is deleted, and when a tenant is offboarded?
  • How long are records retained, and can we extend that for a legal hold?
  • Can tenant A ever see tenant B's events, including through an administrator or a support tool?

Notice that most of these are not about logging at all. They are about integrity, access, retention, and export. Those four properties are what separate an audit trail from a log file.

Record the same shape every time

A consistent event shape is what makes an audit log queryable years later. Decide the fields once, and reject writes that do not conform.

FieldPurposeNotes
Event idUnique, sortable, immutablePrefer a time-ordered id over a database sequence
TimestampWhen the action happenedStore UTC with sub-second precision; record the client's offset separately if it matters
ActorWho actedUser id, service identity, or API key id — never a display name alone
Actor contextUnder what authoritySession id, impersonation flag, support-ticket reference
ActionWhat was attemptedA stable verb from a controlled vocabulary, not free text
TargetWhat it acted onTyped id plus tenant id; avoid embedding mutable labels
OutcomeResultSuccess, denied, or error, with a reason code
Before / afterWhat changedField-level diff for permission and configuration changes
SourceWhere it came fromIP address, user agent, and request id where available
TenantWhich tenant it belongs toMandatory, not inferred at query time

Two decisions in this table are worth extra care. First, use a controlled vocabulary for actions: "role.assignment.create" is queryable forever; "Changed user role" is not. Second, always record the tenant explicitly, even when it could be derived from the target, because derived tenant scoping is where cross-tenant leaks come from.

Make the record append-only

Immutability is the property reviewers test first, because it is the one that determines whether the log can be trusted at all.

  • No update or delete path in the application. The service account that writes events should have insert-only permission. If the same role can write and delete, the trail is only as trustworthy as the role.
  • Separate storage from the product database. Put events in their own store, or at minimum their own schema with different credentials, so an application bug cannot rewrite history.
  • Tamper evidence, not just access control. A hash chain over event ids and payloads, with periodic checkpoints written to a separate location, lets you demonstrate that no record was altered. This is simpler than it sounds: each event stores a hash of the previous event's hash plus its own canonical payload.
  • Clock discipline. Ordering claims depend on timestamps. Use server time, never client-supplied time, and alert on clock skew.
  • Retention is a policy, not a cron job someone remembers. Define the retention window per event class, apply it mechanically, and document what happens at the end of the window — deletion, aggregation, or archival.

Tenant scoping and support access

The most damaging audit-log finding is not a missing event; it is a legitimate event visible to the wrong tenant. Two rules prevent it:

  1. Every query is tenant-scoped by construction. The query layer takes the tenant from the authenticated session, not from a parameter. Support tooling that needs cross-tenant visibility uses a separate, heavily logged path. Tenant scoping is a foundation decision, and the audit trail is only one part of it; our multi-tenant architecture checklist covers the rest.
  2. Reading the audit log is itself an audit event. When a customer administrator exports their trail, or your support engineer opens a tenant's events, that access is recorded. Reviewers ask for this specifically, and it is a strong signal that you take the trail seriously.

What to log, and what not to

Log every state change that affects access, data, or money: authentication and session events, permission and role changes, membership changes, configuration changes, data exports and bulk operations, integration and API key lifecycle, billing-affecting actions, and administrative overrides.

Do not log secrets, tokens, full request bodies, or personal data that the event does not need. An audit log that stores passwords or session tokens becomes a second breach surface. Redact at write time with an allowlist of fields rather than a denylist of patterns, and keep a documented mapping of any pseudonymized identifiers.

Export, retention, and offboarding

Export is where many otherwise good audit logs fail in practice. Reviewers want self-service export because it is a control they can exercise without you. Provide a date-range export in a documented, machine-readable format, with a manifest that includes the event count and a checksum, and generate it asynchronously with a signed download link.

Then answer the lifecycle questions explicitly:

  • When a user is deleted, their actor id remains in historical events as an immutable reference; the user record is pseudonymized, not erased from the trail.
  • When a tenant is offboarded, the trail is retained for the contractual window and then deleted or archived according to the agreement, with the deletion itself recorded.
  • For legal holds, retention can be extended for a defined scope, and the extension is itself an audited action.

Limitations and assumptions

This design assumes you can change your storage and permissions model, which is rarely true retroactively for a system already in production; the practical path there is to start with a new event class in a separate store and migrate gradually rather than rewriting history. It assumes a single primary region or a defensible multi-region ordering strategy — cross-region writes need explicit ordering rules or the hash chain becomes ambiguous. Hash chaining provides tamper evidence, not tamper prevention; if an attacker has write access to both the chain and the checkpoints, the evidence is weaker, so checkpoints should live somewhere with different credentials. Finally, this is an engineering design, not a compliance certification: retention periods and export formats must match the specific framework your customers are asking about, and that mapping belongs with your legal and security owners.

Working with CodeAustral

We design audit trails as part of the platform foundation — insert-only stores, tenant-scoped query layers, hash-chained events, and self-service export that satisfies security questionnaires. If a review has already flagged your logging, see how we build multi-tenant foundations or send a brief with the findings and we will scope the remediation.

Frequently asked questions

Is a database table with a trigger enough for an audit log?

Rarely. A trigger in the same database shares credentials and failure modes with the application, and it usually captures row changes without the actor context that reviewers care about. It is better than nothing for change history, but it will not answer "who did this, under what authority, and from where".

How long should we retain audit events?

Follow the contract first: enterprise agreements typically specify a window, and legal holds can extend it. Absent a contractual requirement, twelve months is a common baseline for access and permission events, with longer retention for financially significant actions. Whatever you choose, apply it mechanically and document it.

Do we need to log read access to customer data?

For most products, log reads of sensitive data categories and all administrative or support reads, not every ordinary read. The test is whether a customer would want to know that this access happened. Bulk reads, exports, and impersonation always qualify.

Can we ship an audit log after the first enterprise deal?

You can, but it is more expensive. Retrofitting actor context, tenant scoping, and immutability into an existing event pipeline usually means running two systems in parallel for a quarter. Adding the event shape and insert-only store before the first enterprise conversation costs a fraction of that.

Your project with CodeAustral

Explore the scope and build your estimate.

Build my estimate