Permission set debt: how orgs quietly lose track of who can see what
Nobody plans an access model that takes four people and a spreadsheet to explain. It accumulates one urgent request at a time.
Admin & OperationsOpen the permission sets list in an org that has been live for five years. You will typically find somewhere between forty and three hundred entries. A handful are well named. Several are named after a person. At least one is called something like Temp Access - DO NOT DELETE, created by someone who left, and nobody will be the one to test that instruction.
This is permission set debt. It is the most common kind of org debt, and unlike a badly built flow it produces no errors, right up until an audit, a departure, or a data incident.
How it accumulates
Nobody chooses this. It arrives through a sequence that is individually reasonable every time.
Somebody urgently needs access to one field. The fastest safe action is a small, targeted permission set. It works, the person is unblocked, everyone moves on. Six months later somebody else needs almost the same thing plus one object, so a second permission set gets cloned from the first. Now two things must be kept in step, and nothing enforces that.
Multiply by five years and a few staff changes. The result is not a design; it is sediment.
The second contributor is the profile-to-permission-set transition. Many orgs are mid-way through moving permissions off profiles, which means access is now expressed in two places with different mental models, and answering "what can this person see" requires checking both.
What it costs
- Onboarding takes days instead of minutes, because nobody can say which sets a new starter in a given role should get.
- Offboarding is incomplete. Deactivation covers the login. It does not answer what that person could see, which is the question an auditor asks.
- Least privilege becomes unprovable. You may well be compliant. You cannot demonstrate it, and in an audit those are the same outcome.
- Every change is risky. Nobody removes a permission because nobody can predict who breaks.
Measure before you cut
Resist the urge to start deleting. Spend a day producing four numbers instead.
1. Permission sets with no assignments. Free wins. Nothing breaks. This is usually 15-30% of the list in an older org.
2. Users with permissions they have never exercised. Login history, setup audit trail and field history give you a defensible picture. Access that has not been used in twelve months is a strong candidate.
3. Users who could modify all data. Count the profiles and permission sets granting broad administrative or data-wide access, then count the humans. This number is almost always higher than leadership expects, and it is the one that focuses attention fastest.
4. Assigned licences that are not being logged into. Not strictly access debt, but you will find it in the same query, and it funds the clean-up.
Restructure around jobs, not objects
The most common structural mistake is one permission set per object or per feature. It sounds tidy and produces a matrix that grows quadratically. Nobody can answer "what does a field engineer get" without reading forty rows.
Organise around the job instead:
| Layer | Contains | Example |
|---|---|---|
| Baseline | What every internal user gets | Chatter, core app access, standard objects everyone reads |
| Job family | The permissions that define a role | Sales rep, service agent, field engineer, finance |
| Elevation | Time-bound or approval-gated extras | Data export, mass delete, integration admin |
A new starter gets baseline plus one job family. That is it. Elevation is deliberately awkward to obtain, because it should be.
Permission set groups are the right mechanism for the job-family layer, they let you compose the group from smaller sets while assigning one thing to the user. Muting permission sets handle the "this role gets everything in the group except X" case without forking the group.
A clean-up that does not break Monday
Five steps, run in this order. It takes a few weeks of elapsed time, not a project.
- Delete the unassigned. No user impact by definition. Do it in a release, not ad hoc, so it is in the record.
- Build the target model in a sandbox. Baseline, job families, elevation. Do not attempt to migrate anyone yet.
- Map every current user to a target job family. This is the real work, and it is a business conversation, not an admin one. Managers confirm their people. Where someone does not fit a family, that is a finding, either the family list is wrong or the person's role is.
- Migrate one department, in production, with the old sets still assigned. Additive first. Nothing is removed yet, so nothing can break.
- Remove the old sets for that department after a week of clean running. Then move to the next department.
Additive-then-subtractive is what makes this safe. Every step is individually reversible, and no user ever loses access in the same change that grants new access.
The reporting you want afterwards
Once the model exists, three saved reports keep it honest:
- Users assigned more than one job family (should be near zero, each one deliberate).
- Elevation assignments older than their review date.
- Permission sets with fewer than three assignees (candidates for merging into a family or retiring).
Review them quarterly. Fifteen minutes, four times a year, is the entire ongoing cost of not being back here in 2029.
If you want a second pair of eyes on an access model before an audit or a platform migration, that is exactly the kind of review our managed services team runs.



