Building compliant software in financial services / The rules that apply to your code
Audit trails that satisfy an examiner
Almost every financial application has an audit log. Far fewer have a trail that answers the questions an examiner actually asks, which are about a specific record on a specific day and the person who accessed it. This page covers the difference, and the six failures that turn a log into an unusable one.
Published August 22, 2026. Editorial.
Key takeaways
- An examinable trail answers: who, what record, what changed from what to what, when, and through which interface.
- Write-only logging misses the most important pattern, which is inappropriate reading of customer data by people who are allowed to read it.
- The trail must be produceable on demand in both human-readable and machine-readable form, without an engineer writing an ad hoc query.
- An audit trail stored where the audited user can modify it is not an audit trail.
An examiner does not ask whether you have logging. They ask a specific question: show me everyone who accessed this customer's record between these dates, and what they did with it.
That question is a good test of an audit trail, because it fails in so many different ways. The log exists but does not cover reads. It covers reads through the main application but not through the admin console. It names a service account rather than a person. It records that a field changed but not what it changed from. It lives in a log aggregator with a thirty-day retention window and the dates in question are four months ago. Or it is all there and producing it takes an engineer two days of ad hoc queries, which is its own kind of failure.
What an examinable trail contains
Six fields, consistently, for every access to a covered record:
Who. A real identity, resolvable to a person. Not a service account, unless the entry also records what triggered the job and on whose behalf it ran. Not the impersonated customer when a staff member is acting as them.
What record. A stable identifier that still resolves after the record is renamed, merged, or deleted. Identifiers that change break historical trails in a way that is hard to repair later.
What happened. Read, create, modify, delete, export. Export deserves its own action type rather than being combined with read, because the risk profile is entirely different: a read shows one record to one person, an export puts a copy outside the system.
From what to what. The prior value and the new value, for changed fields. This is the field most often missing, and it is the one that carries the SEC's re-creation requirement, covered on SEC recordkeeping requirements in your application.
When. An unambiguous timestamp with a zone. Local time without a zone becomes genuinely unusable across a daylight saving boundary years later.
Through which interface. Web app, mobile, admin console, API, batch job, direct database. This is the field teams skip and then wish they had, because when something goes wrong the first useful question is usually how, not what.
The six failures
Write-only coverage. The most common, and the most consequential, because the risk this control exists to manage is largely a read risk. Fraud, snooping, and data theft by authorized users mostly look like reading. The FTC Safeguards Rule requirement to monitor and log the activity of authorized users [1] is not satisfied by a change history.
The impersonation gap. Support tooling that lets staff act as a customer, implemented by assuming the customer's session. Every entry then names the customer. The trail does not merely lack information, it contains false information, and it is false in the direction that hides staff activity.
Coverage by interface rather than by data. Logging is added to the main application because that is where the team works. The admin console, built later or by a different team, writes nothing. Neither does the reporting tool, the data export job, or the internal script somebody runs monthly. The trail is complete for the path nobody was worried about.
The mutable trail. Audit rows in the same database, writable by the same application role that writes everything else. Anybody who can compromise the application, or anybody inside with database access, can edit the record of what they did. A trail the audited party can modify does not do the job it exists to do. Append-only storage, a separate credential, or sending entries to a store the application cannot rewrite are all reasonable answers; none of them is exotic.
Retention shorter than the obligation. The trail lives in a logging product with a default retention of thirty or ninety days, chosen for cost by whoever set it up, while the underlying obligation runs for years. This one is common and easy to fix once somebody notices, and almost nobody notices, because nothing fails when a log expires.
No production path. All the data is there and producing it requires a bespoke query written by the one engineer who understands the schema. That is not an examinable trail. The rule contemplates a system with the capacity to readily download and transfer a record and its audit trail in both human readable and reasonably usable electronic formats [2], and "readily" is an important word in that sentence.
Checking it, rather than believing it
The useful thing about all six failures is that each one is testable, which means each one can be a check that runs on every change rather than a finding in an annual review.
Write a test that exercises each interface, including the ones nobody thinks of as interfaces, and asserts that a trail entry appears with all six fields populated correctly. Write a test that impersonates a customer through the real support path and asserts the entry names the staff member. Write a test that attempts to modify an audit row using the application's own database credentials and asserts it fails. Write a test that reads the configured retention on the audit store and asserts it meets or exceeds the obligation, which catches the day somebody changes it for cost reasons. Write a test that calls the production path and asserts it returns both formats.
These are not sophisticated tests. They are the tests nobody writes, because writing them takes a week that never gets prioritised against feature work. That is precisely the calculation that changed, and it is the argument in why AI-native development fits regulated work.
One design note
If you are building new rather than retrofitting, the cheapest way to satisfy most of this is to make the trail a product of the data access layer rather than something callers remember to do.
Every audit implementation that relies on developers adding a log call in the right place gets worse over time. Not immediately, and not through carelessness, but because there will be a hundred new code paths over three years and some of them will miss it. The version that keeps working is one where reading or writing a covered record cannot happen without producing an entry, because the mechanism that reaches the data is the mechanism that writes the trail.
That is a design decision with a short time to act. It is straightforward at the start and expensive to retrofit once four hundred call sites exist, which is worth knowing before rather than after. Our page on how to audit an existing financial application covers what to do when you are already past that point, and get in touch if you want help either way.
Best for
- Applications holding records a regulator may ask about years later
- Teams with internal tooling that reaches customer data outside the main product
Avoid if
- Nobody has scoped which records are covered, since logging everything in full detail has real cost and no defined end
Check before you decide
- Ask for everyone who accessed one specific record over a date range, and time how long the answer takes
- Check whether reads through the admin console appear at all
- Impersonate a customer through the real support path and read the resulting entry
- Compare the audit store's configured retention against the longest applicable obligation
Common questions
What does an examiner actually ask for from an audit trail?
Usually something specific rather than general: everyone who accessed a particular customer's record between two dates, and what they did with it. That question fails in many different ways, which makes it a good test of whether a trail is genuinely usable.
Why is write-only audit logging insufficient?
Because the risks this control exists to manage are largely read risks. Snooping, fraud, and data theft by people who already have access mostly look like reading, so a change history leaves the main pattern invisible.
What is wrong with storing audit records in the main application database?
Nothing, provided the application's normal role cannot modify them. When audit rows are writable by the same credential that writes everything else, anyone who compromises the application, or anyone with internal database access, can edit the record of what they did, and a trail the audited party can alter does not do its job.
Should exports be logged differently from reads?
Yes, they deserve their own action type, because the risk profile is completely different. A read shows one record to one person in a controlled interface, while an export places a copy outside the system where none of your controls apply.
How should audit logging be implemented so it does not get worse over time?
Make it a property of the data access layer rather than something callers remember to do. Any implementation that depends on developers adding a log call in the right place will miss some of hundreds of new code paths, whereas a design where accessing a covered record cannot happen without producing an entry keeps working.
What six fields belong in an examinable audit trail entry?
Who acted, what record was affected, what happened to it, the prior and new values for anything changed, an unambiguous timestamp with a time zone, and which interface the action came through. Missing any one of these tends to be the reason a trail that technically exists still cannot answer an examiner's actual question.
How is an audit trail different from a general application log?
A general application log is written for debugging and often lives in a system the application itself can overwrite or that deletes entries after a short retention window. An audit trail for compliance purposes needs to be append-only or otherwise unmodifiable by the audited party, and retained for as long as the underlying regulatory obligation runs, not for as long as it is convenient.
What happens if an audit trail cannot be produced quickly when asked for?
The rule contemplates a system with the capacity to readily download and transfer a record and its audit trail in both human readable and reasonably usable electronic formats, and an ad hoc query written by one engineer over two days does not satisfy that word 'readily'. A trail that technically contains the data but cannot be produced on demand functions the same as a missing one during an examination.
References
- [1] 16 CFR 314.4(c)(8): controls designed to monitor and log the activity of authorized users and detect unauthorized access, use, or tampering by such users.
- [2] 17 CFR 240.17a-4(f)(2)(iv): capacity to readily download and transfer copies of a record and its audit trail in both a human readable format and a reasonably usable electronic format.
Related reading
Adding eval tests was the best decision we made
We generate every line of code we release. The single change that made that safe was writing the check before the code, and it turned out to change how we review, how we spec, and how fast we can work.
What good code review looks like when nobody wrote the code
With human code, the author is the first check and review is the second. With generated code, review is the only check. That one change alters most of what a reviewer should be doing.
More in The rules that apply to your code
SEC recordkeeping requirements in your application
The SEC's 2022 amendments gave electronic recordkeeping systems an alternative to write-once storage: a complete time-stamped audit trail that permits re-creation of the original record. It is a far better fit for modern applications, and it is a stricter engineering requirement than most teams realise.
The FTC Safeguards Rule for product teams
Most engineers meet the Safeguards Rule as a security questionnaire. Read the rule itself and it is more specific than that, and it applies to your development process directly: secure development practices, change management procedures, and logging of authorized user activity are named requirements, not recommendations.