Building it right

Access control and least privilege in financial applications

The Safeguards Rule requires limiting authorized users' access only to the customer information they need to perform their duties. That is easy to state, hard to keep true, and almost never verified, because permission models get worse without anyone noticing and nothing breaks when they do.

Published August 22, 2026. Editorial.

Key takeaways

  • Permissions change in one direction. People accumulate access when they change roles and rarely lose the old set.
  • Test authorisation at the endpoint, not the screen. Hidden UI is not access control.
  • Every role needs a negative test: what this role must not be able to reach, asserted explicitly.
  • Emergency access and impersonation paths need the tightest logging, because they are the ones that bypass the model.

Section 314.4(c)(1) requires implementing and periodically reviewing access controls that authenticate and permit access only to authorized users, and limit authorized users' access only to customer information that they need to perform their duties and functions [1].

The first half of that gets built. The second half is where things go wrong, and they go wrong slowly enough that nobody has a moment where it becomes obvious.

Permissions change in one direction

Somebody joins support and gets the support role. Two years later they move to operations and get the operations role. Almost nobody removes the support role, because removing access is a task with no deadline, no requester, and a real risk of stopping someone's work if you get it wrong. So the safe choice for whoever is doing the removing is not to.

Repeat that across a few years and a few dozen people and the permission model in the code no longer describes the access anyone actually has. The roles are clean. The assignments are not. An access review that looks at role definitions will find nothing wrong, because the definitions were never the problem.

The check that finds it is different: enumerate every user, list their effective permissions, and compare against their current function. Not their role, their function. This is uncomfortable because it produces a list of real people with more access than their job requires, and it is the only version of the review that works.

The deeper version of this problem is worse and more interesting. Effective permission is often the union of several grants across several systems: the application role, a database group, a cloud IAM policy, a shared credential in a password manager, membership of a group that grants something nobody remembers configuring. Any single system looks reasonable. The union is what matters and almost nobody computes it.

Test authorisation at the endpoint

The most common authorisation defect in applications we look at is not a wrong rule. It is a rule enforced in the interface rather than in the system.

The button is hidden for users who should not have the capability. The route is not linked. The menu item does not render. And the endpoint underneath answers anyone who calls it, because the check was a rendering decision rather than an authorisation decision. This is not an obscure failure; it is the default outcome when authorisation is implemented by the same person, in the same afternoon, as the screen.

The test that catches it has to work at the interface boundary, not through the UI. For every endpoint, for every role, assert what happens. Most teams test that the permitted role succeeds. Far fewer assert that every other role fails, which is the half that matters, and it is a combinatorial job: endpoints multiplied by roles, including the roles nobody thinks about, like an authenticated user with no assigned role, an expired session, a user from a different tenant, and a valid user whose account was disabled five minutes ago.

That combinatorial shape is exactly the kind of work that used to be too expensive to justify and is now an afternoon. It is one of the clearest cases where the economics we describe in why AI-native development fits regulated work turn into a concrete control.

Every role needs a negative test

Write down what each role must not do. For each role, the list of what it must not be able to reach, as explicit assertions.

This sounds redundant and is not, for a reason that shows up over time. A positive test says the finance role can read invoices, and it keeps passing as the system grows. It says nothing about the fourteen new endpoints added since, and it will keep passing while the finance role quietly gains the ability to read customer identity documents through a new shared component.

The negative assertion is the one that fails when a role's access grows without a decision. It is also the one that documents intent in a way a reviewer can check, because "support must not be able to export customer records in bulk" is a sentence a compliance reviewer can read and agree or disagree with, unlike a permission matrix with two hundred cells.

Emergency access and impersonation

Every financial application ends up with a way to bypass the model. An administrator who can reach anything, a support tool that acts as a customer, a database credential for emergencies. These exist because they are genuinely needed at three in the morning when a customer cannot access their money.

They are also the paths where the access model does not apply, which makes them the paths that need the strongest logging and the clearest boundaries.

The requirements are not complicated. Use of an emergency access path (often called break-glass access) produces its own record, distinct from ordinary activity, that names the person, the time, and ideally the reason. Impersonation records the staff member as the actor rather than the impersonated customer, which is the single most common defect in this area and is covered on audit trails that satisfy an examiner. Elevated access is time-bounded and expires without anybody remembering to revoke it. And somebody reviews the emergency access log on a schedule, because a bypass nobody reviews is just an access path with extra steps.

Service accounts are users too

The account with the broadest access in most financial applications is not a person. It is the service account that the application itself uses, or the integration credential for a third party, or the reporting job that reads everything to build a dashboard.

These get provisioned once, at the start, with generous permissions because narrowing them would have required understanding exactly what the job needed and nobody had time. They then run forever, usually with credentials that do not rotate, frequently with access to more than the current version of the job uses.

Treat them as subject to the same rule. Enumerate them, compute what each actually uses over a representative period, and narrow the grant to match. The gap between what a service account can do and what it does is often the largest single piece of excess privilege in the system, and unlike the human version it produces no awkward conversations.

Making the review continuous

The rule requires periodically reviewing access controls [1], and periodic review conducted by hand tends to become annual, then overdue.

The parts that can be continuous should be. Assert the full endpoint-by-role matrix on every change. Assert that no user holds a permission combination flagged as incompatible, which catches segregation-of-duties problems the moment they appear. Assert that no elevated grant has passed its expiry date. Assert that service accounts hold no permission not exercised in the last quarter, which turns unused privilege into a visible, actionable list rather than a suspicion.

What remains genuinely human is the judgment about whether a given person's access matches their current function. That part is a conversation with their manager, and no check will do it for you. The value of automating everything else is that the conversation happens over a short list rather than a spreadsheet with a thousand rows, which is the difference between a review that happens and one that gets deferred.

If your permission model has been running for a few years and nobody has computed effective access recently, that is the place to start. Get in touch and we will run it with you.

Best for

  • Applications with several distinct internal roles and a support function that can reach customer data
  • Teams that have grown headcount faster than they have removed access

Avoid if

  • The role model itself has not been designed yet, in which case start there rather than testing an accidental one

Check before you decide

  • Compute effective permissions per user as the union across application, database, and cloud IAM, then compare to current function
  • Call a restricted endpoint directly with a role that should not have it, bypassing the UI
  • Check whether impersonation records the staff member or the customer
  • List service accounts and compare granted permissions against what they actually exercised last quarter

Common questions

Why do permission models decay even when nobody does anything wrong?

Because access accumulates in one direction. People gain a role when they change function and rarely lose the previous one, since removing access has no deadline, no requester, and a real chance of stopping someone's work, so the safe choice for whoever would remove it is not to.

Why is testing authorisation through the user interface insufficient?

Because hidden UI is not access control. The most common defect is an endpoint that answers anyone who calls it while the button is hidden for users who should not have the capability, which is the default outcome when authorisation is implemented as a rendering decision in the same afternoon as the screen.

What is a negative authorisation test and why does it matter?

It asserts what a role must not be able to reach, rather than what it can. Positive tests keep passing as the system grows and say nothing about newly added endpoints, so the negative assertion is the one that fails when a role quietly gains access it should never have had.

How should emergency (break-glass) access be handled?

It should produce its own distinct record naming the person, the time, and ideally the reason, be time-bounded so elevated access expires without anyone remembering to revoke it, and be reviewed on a schedule. A bypass nobody reviews is just an access path with extra steps.

Do service accounts count for least privilege?

Yes, and they are frequently the largest single piece of excess privilege in a financial system. They get provisioned once with generous permissions because narrowing them would have taken analysis nobody had time for, then run for years with credentials that never rotate and access far beyond what the current job uses.

How often should an access review happen?

The Safeguards Rule requires periodically reviewing access controls, and a review conducted only by hand tends to move from annual to overdue. The parts that can be asserted automatically, such as the endpoint-by-role matrix and whether elevated grants have expired, should run on every change, leaving the genuinely human judgment about whether a person's access matches their current function for a scheduled conversation.

What is the risk of skipping negative authorisation tests?

A system with only positive tests can accumulate access nobody intended without any test ever failing, because a positive test checks that a permitted role succeeds and stays silent about everything a role should never reach. The negative assertion is what catches access that grows without a decision, such as a role quietly gaining access to a new component that exposes customer identity documents.

How is testing authorisation at the endpoint different from testing the UI?

Endpoint testing calls the underlying route or API directly with a given role and checks the response, bypassing whatever the interface shows or hides. UI testing only confirms what a button displays, and a hidden button says nothing about whether the endpoint behind it still answers a request from a role that should not have access.

Start a project