Admin & Operations

Org health

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.

An analyst reviewing several screens of data at a deskAdmin & Operations

Open the permission sets list in an org that has been live for five years. It is longer than anybody expects and most of it is unfamiliar to whoever is looking. A few entries are well named. Several are named after a person, and some of those people have left. At least one is called something like Temp Access - DO NOT DELETE, created by an administrator nobody can identify, and nobody wants to be the one who tests that instruction.

This is permission set debt. Unlike a badly built flow it produces no errors at all, so it stays invisible until an audit, a departure or a data incident, and then it is the only thing anybody wants to talk about.

One separation before going further. This is not about how the platform decides who can see a record: that order is fixed, and it is covered in who can see what. This is about what happens to a permission model over the years afterwards, and it happens in well designed orgs too, faster, because a good model gets used more.

The specific ways sprawl starts

Nobody chooses this. It arrives through a handful of sequences that are reasonable every time, and each leaves a different residue.

Clone of a clone. Somebody urgently needs one field, so a small targeted permission set gets made. Six months later somebody needs almost the same thing plus an object, and the quickest route is to clone the first and add to it. Now two things must be kept in step and nothing enforces that. Clone the second and there are three. They drift, and the differences between them record what each requester asked for on the day.

The per-request grant. A ticket arrives, access is granted directly to the user, the ticket closes. Nothing wrong happened, but the grant is attached to a person rather than to a reason, so the next person in that job does not inherit it and no later review can tell whether it reflects the role or the requester.

A permission set per person. The endpoint of the pattern above, and more common than anybody admits. Each named set is accurate the day it is written and generalises to nobody. The org holds as many access definitions as it has employees, and replacing someone means reverse engineering their set.

Project-named sets nobody retires. A programme needs elevated access for its build team, so a set is created with the programme name on it. The programme ends. The set does not, because it has no expiry, no owner once the project closes, and no moment at which anybody was meant to remove it.

The mover nobody unwinds. Joiners and leavers are usually handled. Movers are the gap. Somebody transfers from service to sales, gains everything sales needs and keeps everything service gave them, because nothing asks what should be taken away. Run that for a few years and a population builds up whose access is the union of every job they have done.

Underneath all five sits the half-finished migration from profiles to permission sets, which leaves access expressed in two places at once, so one question about one user means checking both and knowing how they combine.

PatternHow it shows in the listWhat it leaves behind
Clone of a cloneNear-identical names, small unexplained differencesSeveral sources of truth for one grant, drifting apart
Per-request grantDirect assignments with no matching setAccess attached to a person, not to a reason
A set per personSets named after employeesAs many access definitions as there are staff
Project-named setsProgramme names, dates, code namesElevated access with no owner and no expiry
The unwound moverUsers holding two or more roles' worth of accessQuiet accumulation no leaver process catches

What it costs

  • Onboarding takes days rather than minutes, because nobody can say what a new starter in a role should get, so somebody copies whatever the last person had.
  • Offboarding is incomplete. Deactivation covers the login. It does not answer what that person could see, which is what 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 becomes risky. Nobody can predict who breaks, so the model only ever grows.

Measure before you touch anything

Resist the urge to start deleting. Spend a day producing four numbers instead. They give you the size of the problem and the end of it that is safe to pick up first.

1. Permission sets with no assignments. Assignment records answer this directly, and every one is a free win, because nobody holds it.

2. Users holding permissions they do not exercise. Harder, and worth the effort. Login history says who is active at all. Setup audit trail, field history, and the reports, list views and integrations built on the object say whether the capability is used by anybody. Access untouched for twelve months is a candidate, not a verdict.

3. Users who can view or modify all data. Count the humans, not the sets that grant it. The number is almost always higher than leadership expects, and it focuses attention fastest.

4. Assigned licences with no logins. Not access debt exactly, but it surfaces in the same query, and recovering the spend often funds the clean-up.

What a bad measurement looks like

Access reviews fail at the measurement stage more often than at remediation, and they fail in recognisable ways.

Counting rows instead of assignments. A long list sounds alarming and means nothing alone. A fraction of those sets may carry every real grant while the rest are inert. What matters is how many users hold how many things.

Reading the permission set list and stopping there. Profiles still carry settings, groups compose sets into something larger, and muting sets subtract from inside a group. A count that ignores any of those reports a fraction as the whole. What a set contains is the unit of risk, so a review that never opens them is measuring labels.

Measuring in a sandbox. Sandboxes diverge from production immediately, and assignments are not a faithful copy. Measure in production, and use the sandbox to test the remedy.

Treating held access as used access. The consequential one. What a user holds says what they could do and nothing about what they do, and that gap is the entire opportunity in a clean-up.

Record the four numbers with a date and the definition used. A number with nothing beside it cannot be improved, because nobody can tell whether it moved.

Why deleting is the fix that causes the outage

The instinct after measuring is to start removing, and it is wrong for a structural reason rather than a cautious one.

Permissions are additive. Effective access is the sum of everything granted from every source, so adding a grant can never take anything from anybody. Removal is the only operation that can, and its effect is hard to predict from the thing being removed, because you are not removing a capability. You are removing one contributor to a sum, and whether anybody notices depends on what else contributes.

The silent minority. A set held by many people may be the only source of a grant for a handful of them. Everyone else is covered elsewhere, so testing with a typical user proves nothing about the ones who break.

The delayed break. Not everything is exercised daily. A quarter-end close, an annual renewal run: remove the permission in July and the failure lands in September, when nobody connects the two.

The thing that is not a person. Integration users and scheduled jobs fail differently. A person raises a ticket within the hour. A job logs an error nobody watches and stops writing records.

The fourth cost is not technical. Users forgive a slow system. They do not forgive one that took something away with no warning, and a single bad removal buys years of resistance to the next sensible change.

The sequence that does not break Monday

Additive first, subtractive last, never both in the same change. Five steps, a few weeks of elapsed time rather than a project.

  1. Delete the unassigned. No user impact by definition. Do it through the release process rather than ad hoc, so it lands in the record.
  2. Build the target model in a sandbox. Baseline, job families, elevation. Migrate nobody yet. This step agrees the shape, which is the next section.
  3. Map every current user to a target job family. The real work, and a business conversation rather than an administrative one. Managers confirm their own people. Where somebody does not fit a family, that is a finding: either the family list is wrong or the role is.
  4. Assign the new sets in production, one department at a time, with the old ones still in place. Nothing is removed, so nothing can break. Users notice nothing, which is the point.
  5. Remove the old assignments for that department after a week of clean running. Early in the working week, when that team is working, with a named person able to restore any grant in minutes. Then the next department.

Step four is where user access policies earn their place. They apply the target assignment to a defined population as a rule, rather than clicking through hundreds of users and hand-making fresh exceptions during the exercise meant to remove them.

Every step is individually reversible, and no user loses access in the same change that grants it. That is the whole safety property, worth protecting even on the day a step feels unnecessary.

Restructure around jobs, not objects

The common structural mistake is one permission set per object or per feature. It sounds tidy and produces a matrix that grows with the product of roles and objects. Nobody can say what a field engineer gets without reading dozens of rows, so nobody tries, and the model stops being consulted.

Organise around the job instead. Four layers, and every user is the sum of a small number of them.

LayerContainsAssigned toExample
BaselineWhat every internal user getsEveryone, automaticallyCore app access, the objects everyone reads
Job familyThe permissions that define a roleOne per user, normallySales rep, service agent, field engineer, finance
ElevationTime-bound or approval-gated extrasFew people, with a review dateData export, mass delete, integration admin
SystemAccess for an integration or job, not a personOne per integrationA named connection with its own justification

A new starter gets the baseline plus one job family. Somebody who does not administer the org should be able to say that in a sentence. Elevation is deliberately awkward to obtain and carries an expiry, because access that never expires is access nobody reviews.

Permission set groups are the mechanism for the job-family layer. They compose a role from smaller reusable sets while the user receives one assignment, which makes the model readable from both ends: the administrator sees composition, the auditor sees one thing per person. Muting permission sets cover the role that needs everything in a group except one capability, without forking the group into a near-duplicate and restarting the clone cycle.

The job family list decides whether any of this survives. Keep it short enough that a manager can pick from it unaided, and derive it from how the business describes its own roles rather than from the org chart.

Keeping it out, which is a request process rather than a clean-up

A clean-up with no process change buys about eighteen months. Sprawl restarts with the first urgent request, because the conditions that produced it are untouched.

The intervention is small. Add one question to the access request: which job family does this belong to. Most requests answer it immediately, and those become a change to the family rather than a grant to a person, so the next person in that role inherits it and nobody has to remember. The ones that cannot answer it get an owner, a written justification and a review date before anything is granted.

Then close the mover gap, because that is what refills the org. A role change has to trigger a removal as well as an addition, and the only reliable way to get that is for the target state to be a family rather than a list, so the change is a swap rather than an accumulation.

When you genuinely cannot tell why somebody has access

This happens often, and it is where clean-ups stall. Somebody holds something, nobody knows why, and both instincts are wrong: delete it and find out, or leave it alone forever.

Find the source. Effective access is a sum, so establish which term produced it: a directly assigned set, a permission set group, the profile, or the licence. The mystery often dissolves here, because the grant is inherited from something that does have a reason.

Find the event. The setup audit trail records who assigned what and when. A date is usually enough, because it lands in the same week as a go-live, a reorganisation or a departure, and somebody remembers once given the date.

Ask the person. Not whether they need it, which always returns yes. Ask what they would be unable to do in a normal week without it. A specific answer is a justification and belongs in the job family. A vague one says the grant is residue.

If none of that answers it, record it rather than resolving it by deletion. An unexplained grant with a named owner and a review date is an honest, auditable state, and a better position than a confident removal that breaks a quarterly process three months later. Where the risk cannot wait, remove it as a small controlled experiment: one cohort, early in the week, with the restore written and ready before the removal runs.

The reporting that keeps it honest

Once the model exists, three saved reports carry the ongoing cost.

  • Users assigned more than one job family. Close to none, and each one deliberate and documented.
  • Elevation assignments past their review date. This is what stops elevation quietly becoming permanent.
  • Permission sets with very few assignees. Candidates for folding into a family or retiring, and the earliest sign the clone cycle has restarted.

Review them quarterly. A short meeting four times a year is the entire running cost of not being back here in a few years, and it is the only part of this that has to be permanent.

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.

Sources

  1. Trailhead: Permission Set Groups
  2. Trailhead: User Access Policies
  3. Trailhead: User Management

Common questions

Answered, directly.

The questions this piece settles about Admin & Operations, answered in full on this page.

Additively first, then subtractively, and never both in the same change. Delete only the sets with no assignments, build the target model in a sandbox, assign the new permission sets in production while every old assignment stays in place, and remove the old ones for one department at a time after a week of clean running. Removal is the only step that can take access away, so it travels alone and in a window when the affected team is working.

Start with permission set assignment records, which tell you which sets nobody holds. Those are safe to retire. Unused is a harder question than unassigned, because holding a permission is not the same as exercising it, so pair the assignment data with login history, setup audit trail and the reports and list views built on the objects in question before treating any assigned set as dead.

Do not delete it. Trace the assignment source first: a directly assigned permission set, a permission set group, a profile or the licence. Check the setup audit trail for who granted it and when, then ask the user what they would be unable to do without it. If none of that produces an answer, record it as an unexplained grant with a named owner and a review date, and remove it in a controlled window with a pre-written path to put it back.

Free architect conversation

Talk to an architect, not a sales rep.

Salesforce org health check. 60 seconds to brief us, and a certified architect replies within one business day.

What is making the org hard to run?

Pick the closest fit. The health check is free and the findings are yours whether or not we do the work.

Which parts of the org are the problem?

Optional. Choose any that apply, or skip ahead.

Where does your org stand today?

Optional. A few sentences is plenty: what is working, what is stuck, and what you want to be true. Or skip ahead and tell us on the call.

Who should the architect reach?

A certified architect will reply to these details.

Takes about 30–60 seconds · No obligation · Architect replies within one business day

Protected by reCAPTCHA. Google's Privacy Policy and Terms apply.

More from Insights

Read by desk

Ten desks, one delivery team. Every piece is written by the people who do the work.