
Guide
Access Control
Permission management: how to keep user access right after you grant it
Guillaume Rigal
Most teams are good at setting permissions and have no process for managing them. Here is the difference, the access review checklist that closes it, and where fine-grained control is worth its cost.
Granting access takes five minutes. Somebody asks, somebody with the right level says yes, and the checkbox moves. The hard part is the year that follows: proving the grant was right, finding it again when the person changes team, and noticing when it stopped being right.
That work is permission management, and it is a different job from setting permissions. Most teams do the granting well and have no process at all for the rest. The bill arrives at the first serious access review, when nobody can finish the exercise because the permission model cannot say what it meant. And now that AI agents and automations hold grants of their own, you also have to decide who owns those grants and how they get reviewed, before an auditor asks you.
We start with the model: what fine-grained actually means, what it costs, and where field-level control is worth the trouble. Then we move to the operation: how to run an access review, where access decays between reviews, and what an auditor asks for when they arrive.
What is fine-grained permission management?
Fine-grained permission management means controlling access along four dimensions at once: which collection, meaning which table of customers or payments or disputes, then which records inside it, which fields on those records, and which operation somebody may run. A coarse model controls one or two of them and rounds everything else up, which is how people end up holding rights nobody ever decided to give them.
"Fine-grained" gets used loosely. Concretely, it is these four.
Dimension | The coarse version | The fine-grained version | What coarse costs you |
|---|---|---|---|
Collection | Access to the tool | Access to named collections of data | The new collection somebody added last month is visible to everybody |
Record | All rows, or none | A scope resolved against whoever is logged in | One support analyst can open every account, in every market |
Field | The whole record | Named columns, hidden or masked | Full bank details sit on a screen forty people open daily |
Operation | One "write" flag | Read list, read detail, create, update, delete and export, decided separately | Export travels with edit, and export is how data leaves the building |
User access rights only become a question you can answer when all four are written down somewhere. If your model can express the first two and nothing else, then "what can this person do?" has no accurate answer, and an auditor will ask you exactly that.
Fine-grained access control costs more than it looks
Every dimension you add multiplies against the others. Five roles and four scopes is twenty combinations. Add field rules on two collections and you are past the point where anybody holds the whole picture in their head.
This is the real trade-off, and vendors tend not to mention it. A model too fine to be understood is exactly as unreviewable as one too coarse to be correct. Both fail the same test: somebody has to be able to look at the current state and say whether it is right.
So do not go fine everywhere. Go fine where the risk concentrates:
Money movement. Refunds, payouts, credits, anything with a threshold.
Personal data. Anything a regulator would call PII.
Irreversible operations. Deletion, account closure, anything with no undo.
Export. The one operation that moves data outside every control you have.
Stay coarse everywhere else, and accept that a few people can see a few things they do not strictly need. That is what internal tool governance looks like in practice, rather than in a policy document.
Redaction and masking are where fine-grained earns its keep
The four dimensions are not equally hard. Collection and operation are usually a configuration screen. Field-level control is the one teams postpone, and it is the one that turns out to matter, because sensitive columns do not live in sensitive tables. They live inside ordinary records that ordinary people open all day.
A support analyst opening a customer record needs the name, the email, the dispute history and the last four digits of the card. They do not need the full card number, the national ID, or the date of birth. Those three columns sit in the same row as everything they do need.
Two mechanisms, and the difference matters:
What it does | Use it when | |
|---|---|---|
Hiding | The field is not returned and not rendered. The reader cannot tell it exists. | The field is irrelevant to the job, and its existence is not |
Masking / redaction | The field is returned partially: | The reader needs to confirm or match a value without seeing it |
Masking is the one people underuse. Most support work needs confirmation, not disclosure. "Is the card ending 4471 the one you used?" resolves the ticket without the full number ever reaching a screen, a screenshot, or a support tool's own logs.
One rule that saves an incident later: apply the control at the point the data is read, not in the interface. A field hidden by the front end is still in the API response, and the API response is what ends up in a browser console, a log file, or an export.
This is worth testing rather than assuming, because plenty of tools, Forest included, control field visibility through the layout rather than through the data layer. Hiding a column from a screen is not the same as removing it from the response, and an AI agent calling your API is not looking at your screen.
The reliable pattern, when a field must not leave, is to make it absent rather than hidden. Expose a database view containing only the safe columns and treat that view as its own collection. The sensitive columns are not filtered out downstream, they were never selected, so nothing that queries the collection can ask for them. In Forest that view becomes a collection like any other, with its own role and its own scopes, which means an agent connected through Forest's MCP Server cannot reach what the view does not contain. Reading the onboarding status of a customer without their name or date of birth is exactly this: a view, not a permission.
How do you run an access review?
An access review answers one question: is what everybody holds today still what they should hold? Run it quarterly for most systems, monthly for anything touching money or personal data.
Six steps, in this order.
Export the current state. All users, their roles, their scopes, in one file. If you cannot produce that file without clicking through a settings screen one user at a time, stop here and fix that first. Everything below depends on it.
Group by role, not by person. Twelve people holding the same role is one decision to review, not twelve. Reviewing person by person is how reviews get abandoned in week two.
Check leavers and movers first. Leavers are usually handled. Movers almost never are.
Hunt the exceptions. Any role held by exactly one person, any permission granted outside a role, any account created in a hurry during an incident. Exceptions are where the model stopped describing reality.
Include the non-human accounts. Service accounts, API keys, anything an automation or an AI agent authenticates with. They are granted once, reviewed never, and they do not leave the company when their owner does. What each of those actually is, and why a shared key is not an identity, is in our guide to user authentication.
Record the decision, not just the change. "Reviewed, kept, because she still handles DACH disputes" is what makes the next review take an hour instead of a day. A change log tells you what moved. It does not tell you why anybody thought it was correct.
The last step is the one teams skip and the one auditors ask for. What they want is not proof that access is correct today. It is proof that somebody competent looked and decided.
Joiners, movers and leavers: where the model quietly decays
Access does not usually go wrong at the moment it is granted. It goes wrong afterwards, and almost always at one of three points.
Joiners are the safest of the three, because somebody is paying attention. The failure here is copying. A new analyst gets set up like the colleague sitting next to them, and that colleague has been here four years and holds three roles they no longer use. Cloning a person copies their accumulated exceptions. Assign from a role, never from a colleague.
Movers are the one nobody handles. Somebody moves from support to compliance, gains what compliance needs, and keeps everything support had, because removing access requires somebody to notice that they should. Nobody is incentivized to notice. Six moves later you have a person who can do almost anything, and no single decision anyone would defend.
Leavers are handled at the identity layer, which is the easy half. What survives a departure is everything that was not attached to their login: the API key they created for a script, the automation running under their account, the shared credential in a team password manager. Those keep working, and now they belong to nobody. Grants held by people who were never on your payroll in the first place are a harder version of the same problem, covered in permissions beyond your own team.
The fix for all three is the same and it is unglamorous. Access is granted through a role, never directly. Roles are attached to a team or a group, never to a person by hand. And every change of team triggers a re-grant rather than an addition. Keeping that role list from multiplying is its own discipline, covered in our guide to role-based access control.
Put the grant in code, not in a settings screen
A permission model that lives only in a UI has no history. You cannot see what changed last quarter, you cannot see who approved it, and you cannot compare it against what you meant.
Declaring roles, scopes and field rules as code fixes all three for the ordinary reason: the change goes through a pull request, somebody reviews it, and the trace is permanent. It also turns the access review from an exercise into a comparison. You are no longer reading the whole model every quarter. You are reading what moved.
The settings screen still matters, because most of the people who need to read the model do not read code. The point is which one is the source of truth.
What does an auditor actually ask for?
Auditors ask for less than teams expect, and they ask for it more specifically. In practice it comes down to four things:
The current state, exported. Who holds what, as a file, not as a demonstration.
Evidence of review. Dated, with a name attached, at the interval your own policy claims.
A sample traced end to end. Pick one person, show the grant, show who approved it, show the review that confirmed it.
The exceptions, explained. Not the absence of exceptions. The list, with a reason next to each.
None of that is about having a perfect model. It is about being able to produce evidence on request. A team with a slightly coarse model and a real review cycle passes. A team with an elegant model and no record of anyone ever looking at it does not.
Which is largely a question of where the model lives. One permission model, on infrastructure built to be audited, produces that evidence as a byproduct. A model spread across a homegrown admin panel, a few scripts and a spreadsheet turns every audit into a project, every time.
This is the least glamorous argument for not building your own backoffice, and the one teams recognize late. Forest keeps roles, scopes, field rules and the activity log in one place on top of your existing data, so the export an auditor asks for is a property of the system rather than a ticket. It is the layer regulated fintechs including Qonto and Swan run their operations on.
Frequently asked questions about permission management
How often should you run an access review?
Quarterly is the working default. Monthly for systems handling payments or personal data. Whatever you pick, put it in a calendar with an owner, because the interval matters less than the fact that it happens without anybody deciding to start it.
What is the difference between user access management and permission management?
They are usually used for the same thing. Where teams do split them, user access management covers identity and lifecycle, meaning joiners, movers and leavers, and permission management covers what those identities are allowed to do once they are in. You need both, and the second is the one that decays quietly.
Do service accounts need to be in the access review?
Yes, and they are the most commonly omitted item. A service account has no manager to notice it, no departure date, and often no owner after the person who created it moves on. Give every one of them a named owner and review them on the same cycle as people.
Access is a decision you have to defend later
Granting is the easy half, and it is the half most tooling is built for. The half that matters is the one where somebody asks, twelve months later, why this person could do this thing.
Build the model fine enough to say what you actually meant, in the places where being wrong is expensive. Then build the habit of reading it back.
The wider model this sits inside, including how platform capability and data access come apart, is in our guide to user roles and permissions.
More questions about permission management
How do you apply least privilege without blocking people?
Least privilege fails when it is treated as a target instead of a default. Start every role at nothing, add what the job demonstrably needs, and make asking for more take minutes rather than days.
The reason teams over-grant is not that they disagree with the principle. It is that requesting access is slow, so everybody asks for everything up front, just in case. Fix the request path and the grants shrink on their own. The rule itself, and the five that sit alongside it, are in our guide to user roles and permissions.
How do you revoke access properly?
Revoking is not deleting a row. Three things have to happen together: the grant is removed, any session or token already issued under it stops working, and the removal is recorded with a reason.
Miss the second and the person keeps their access until their token expires, which in many systems means until they choose to log out. Miss the third and next quarter's review cannot tell a deliberate removal from an accidental one.
Does fine-grained permission management slow an application down?
Rarely, and almost never where teams expect. A role lookup is cheap. What costs is resolving a scope into a query filter on every request, and the usual fix is to cache the resolved permission set for the length of the session rather than to simplify the model.
Be deliberate about what you cache and for how long. A permission set cached for an hour means a revocation takes an hour to take effect, and that is the trade you are actually making.
Which regulations require access reviews?
Most of the frameworks a regulated company deals with expect them, though not all use the phrase. SOC 2 and ISO 27001 both ask for evidence that access rights are reviewed periodically and that removals happen when somebody leaves. PCI DSS sets an explicit interval. GDPR prescribes no cycle, but Article 32 requires appropriate technical and organizational measures, and a permission model nobody has reviewed is difficult to defend as appropriate.
In practice the answer is the same whichever applies to you: pick a cycle, write it down, and keep the evidence that it happened.
