Building compliant software in financial services / Auditing what you have
How to audit an existing financial application
An audit that starts by reading the code finds what the code does. An audit that starts from the obligations finds what the code fails to do, which is the question you are actually asking. This page covers the second kind, in the order that works.
Published August 22, 2026. Editorial.
Key takeaways
- Start from the obligations, not the codebase. The direction determines what you are capable of finding.
- Map every place covered data is stored before tracing controls. The list is always longer than expected.
- For each obligation, demand a single named mechanism. 'Handled in several places' is a finding, not an answer.
- Close each confirmed gap with a check, so the finding cannot silently reappear after the audit ends.
Most internal compliance audits produce a document and no change. The reason is usually the starting point: the audit begins with the system, walks its controls, confirms they exist, and concludes that things are broadly fine. That process cannot discover an obligation the system never addressed, because nothing in it ever mentions the obligation.
This is the order that works instead.
Step 1: scope the obligations, on paper, before opening the code
Write down the obligations that apply to the running system, with the regulatory text quoted next to each one. Not summarised. Quoted, because the summary is where the requirement loses its most important words, and "any individual accessing any information system" [1] becomes "users need MFA."
Two rules for this step. It happens before anybody looks at the code, so that what you find does not shape what you look for. And the scoping of which obligations apply comes from counsel, not from engineering, because that is a legal determination and getting it wrong invalidates everything that follows.
The output is a list. Ours typically covers recordkeeping and retention, audit logging, access control and authentication, encryption of data at rest and in transit, secure disposal, change management, and the testing cadence. Yours depends on your business.
The method for turning each into a testable assertion is on how to translate a regulation into engineering requirements.
Step 2: map where covered data actually lives
This step is boring and finds more than any other.
Enumerate every place a covered record is stored. The primary database. Read replicas. The analytics warehouse. Backup snapshots and their tiers. Object storage used for exports and attachments. The search index. The cache, if records are cached. The log aggregator, if any record content has ever landed in a log line. Third-party services: monitoring, error tracking, support tooling, email providers that received a record in a template, and any integration that syncs data for context. Local copies, including production samples pulled for debugging.
For each, record what it holds, who can reach it, whether it is encrypted, and what its retention is.
This list is nearly always longer than the team expects, and the surprises are where the gaps concentrate. A retention policy enforced perfectly on the primary database means little when the same records sit in an export bucket with no lifecycle rule and in a support tool with its own retention. The same applies to encryption: "all customer information held or transmitted" [1] is a claim about this whole list, not about the database.
Do this before tracing controls, because you cannot evaluate a control without knowing what it is supposed to cover.
Step 3: trace one mechanism per obligation
Now go obligation by obligation and find the mechanism in the running system that guarantees it. The specific requirement is one mechanism, named, that a person can identify and show.
The reason for insisting on one is that the answer "it is handled in a few places" is itself a finding. A control implemented in several places by convention is a control that will be missed by the next code path, and the audit's job is to notice that now rather than after it happens. Write it down as a gap even though the behaviour is currently correct, because the property you need is that it cannot break, not that it works today.
For each mechanism, ask what happens on the paths that go around it. If read logging is implemented in a data access decorator, what calls the database without going through it? If retention is a scheduled job over one table, what about the replica? If authorisation is checked in a middleware, which routes are outside it? These questions produce the real findings.
Step 4: test the claims rather than accepting them
Everything so far is reading and talking. This step is where an audit becomes worth its cost, because the gap between what a system is believed to do and what it does is the entire subject.
Run the full cycle tests. Create a record, modify it, delete it through the path a real user would use, and reconstruct it from the audit trail alone [2]. Read a customer record through every interface, including the admin console and the reporting tool, and check what appears in the trail. Impersonate a customer through the real support path and read the actor field. Call a restricted endpoint directly with a role that should not reach it, bypassing the interface entirely. Export data and check whether the export is logged as an export. Take a record that should have passed its disposal date and search every location from step 2 for it.
Each of these is a small experiment with a definite answer. Collectively they replace opinion with observation, and they routinely contradict what everybody in the meeting believed an hour earlier. That is not a failure of the team; it is what happens when a system has been running for years and nobody has been in a position to ask.
Step 5: close each gap with a check, not a ticket
The failure mode of audits is that they end in a report. The findings get tickets, some get fixed, and eighteen months later the same audit finds the same things, because fixing a gap does not prevent it reopening.
Every confirmed gap should close with two things: the fix, and an assertion that fails if the gap returns. The assertion is the durable part. It converts a one-time finding into a permanent property, and it accumulates, so the second audit starts from a much stronger position than the first.
This is also where the audit turns into evidence rather than a document, for the reasons on using evals as compliance evidence. A report says you looked. A suite of assertions with a run history says the property has held continuously since the date you fixed it, and that is a much better answer to the question an examiner is actually asking.
How long it takes and what it costs
An audit of this shape on a mid-sized application is a matter of weeks rather than months, and most of the elapsed time is steps 2 and 4: enumerating where data lives, and running the experiments. Step 1 is an afternoon with the right people in the meeting. Step 5 is where the work continues after the audit ends, and where the cost sits, because closing gaps means changing a system that is in production.
The thing worth knowing in advance is that the findings are usually not evenly distributed. Most systems are broadly fine across most obligations and have two or three areas where nothing was ever implemented, typically read logging and retention across copies. Budgeting as though every obligation needs work is wrong; so is assuming a clean result because the first few checks passed.
If you want this run against your product, or want the obligation list built so you can run it yourself, get in touch. We also cover the adjacent question of what an acquirer or investor will look for in technical due diligence.
Best for
- Applications in production for years, particularly after several team or ownership changes
- Teams facing an examination, an enterprise security review, or diligence
- Products about to undergo a significant rebuild, where the findings shape the specification
Avoid if
- The obligations have not been scoped by someone qualified to scope them, since the audit inherits that error entirely
Check before you decide
- Insist the obligation list is written before anybody opens the codebase
- Require one named mechanism per obligation, and treat 'handled in several places' as a finding
- Run the experiments rather than accepting descriptions of behaviour
- Close every confirmed gap with an assertion, not only a fix
Common questions
Why should a compliance audit start from the obligations rather than the code?
Because the direction determines what the audit is capable of finding. An audit that walks the system's existing controls confirms they exist and can never surface an obligation the system never addressed, since nothing in that process mentions the obligation.
What is the highest-value step in an application compliance audit?
Enumerating every place covered data is stored, before tracing any controls. The list is nearly always longer than the team expects, and retention and encryption gaps concentrate in the locations nobody listed: export buckets, backup tiers, warehouses, support tools, and error-tracking services.
What does it mean if a control is implemented in several places?
It is a finding, even when the current behaviour is correct. A control maintained by convention across multiple call sites will be missed by the next code path added, and the property you need is that it cannot break rather than that it happens to work today.
Why should each finding close with a check rather than a fix?
Because a fix addresses the instance and a check addresses the class. Without the assertion, the same audit finds the same gaps eighteen months later, whereas with it each closed finding becomes a permanent property and the next audit starts from a stronger position.
How long does this kind of audit take?
Weeks rather than months for a mid-sized application, with most of the elapsed time in enumerating where data lives and running the experiments. The remediation afterwards is where the real cost sits, because closing gaps means changing a system already in production.
Who should scope which obligations apply before an audit begins?
Counsel, not engineering. Scoping which regulations apply to a given business is a legal determination, and getting it wrong invalidates everything that follows, since the rest of the audit only ever checks the system against the obligation list it was handed. Engineering's role starts after that list exists: converting each obligation into a testable assertion and tracing it back to the specific provision it satisfies.
Is reading the codebase a good starting point for a compliance audit?
No, because an audit that starts with the code and walks its existing controls can only confirm that what was built exists, and it cannot surface an obligation the system never addressed in the first place. Starting from the regulatory obligations and asking the system to demonstrate each one finds the gaps that a code-first walkthrough structurally cannot see.
What is the difference between an audit finding and a closed finding?
An audit finding is a documented gap, typically followed by a fix and a ticket. A closed finding also includes an assertion that fails automatically if the same gap returns, which is what prevents the next audit, eighteen months later, from rediscovering the identical problem in a system that looked fixed.
References
- [1] 16 CFR 314.4(c): encryption of all customer information held or transmitted, MFA for any individual accessing any information system, logging of authorized user activity, and secure disposal.
- [2] 17 CFR 240.17a-4(f)(2)(i)(A): the audit trail must permit re-creation of the original record if it is modified or deleted.
How a build like this runs
Related reading
How to prepare for technical due diligence before a raise or sale
Technical due diligence is where a deal can quietly fail. Here is what investors' technical reviewers actually look at, how to prepare before they do, and the warning signs that worry them.
A practical pre-launch security review for a small team
You do not need perfect security to launch. You need to check the few basics that find most real problems, and to know when the risk is big enough to bring in a specialist.
When to rewrite vs refactor legacy code (and why the big rewrite is usually wrong)
The full rewrite feels clean and honest, and it is almost always the wrong instinct. Here is when a rewrite is genuinely justified and how to replace old code piece by piece instead.