Skip to main content
New tool CRON Expression Builder — preview next run times before you schedule Apex. Open the builder →
Interview prep

Security and sharing interview questions and answers

OWD, sharing rules, apex sharing, FLS and permission sets.

13 questions · 5 of them scenarios

Junior round

Junior Security & sharing questions

Security & sharing junior

Walk me through how a user ends up with access to a record.

Testing How you think about the model — as a floor that gets raised, or as a list of features to recite.

junior

Organisation-wide defaults set the floor. If Account is Private, nobody sees anybody else's Accounts to start with, and then access gets added on top: the role hierarchy gives managers their team's records, sharing rules open records up to a group or role, manual sharing handles the one-off case, and Apex sharing covers what none of those can express. Nothing in that list ever takes access away — it only widens.

mid

I hold it as one rule: the org-wide default is the only thing that restricts, everything else grants. That is why the answer to "why can this person see it" is a hunt, and why the answer to "why can they not" is usually the default doing exactly what it was set to. Two parts catch people. Grant Access Using Hierarchies is a per-object setting and it is off for some standard objects, so a manager is not automatically seeing everything below them. And a Master-Detail child has no default of its own at all — it is Controlled by Parent, so its access is whatever the parent's is, and changing the child's sharing means changing the parent.

Security & sharing junior

When does something belong on a profile rather than in a permission set?

Testing Whether you have untangled somebody else's permission model, or only read the definitions.

junior

A user gets exactly one profile and as many permission sets as you like, so the profile is for the things that are true of everyone doing that job, and permission sets are for the extras. In practice that means I keep profiles as bare as the org will let me and grant almost everything through permission sets, because taking a permission away from one person is impossible when twenty people share the profile.

mid

My rule is that a profile holds what cannot live anywhere else, and everything else goes in a permission set. Login hours and IP ranges, the default record types, the default app — those are profile-only, so they stay. Object and field access moves out, because a profile change is a change for every user assigned to it, and the moment two people need slightly different access you are cloning a profile. The org I inherited had 23 profiles differing by a checkbox each, and nobody could tell you what any of them meant. Permission set groups make the additive model manageable, and a muting permission set inside a group is how you subtract without cloning anything.

Deep dive on this
Security & sharing junior scenario

A user says they cannot see a record their colleague can see. How do you work it out?

Testing Where you look first when two users with the same permissions get different answers.

What they tell you

Both users have the same profile and the same permission sets. The record is an Opportunity owned by a third person. One of them opens it, the other gets an insufficient privileges error.

junior

Same profile and same permission sets means object access is identical, so the difference has to be record level. I would open the Sharing button on the record, which lists who has access and why, and compare that against the two users' roles. Usually the colleague sits above the owner in the hierarchy and the user does not, or a sharing rule points at a group only one of them belongs to.

mid

The fact that permissions match is the useful part of the report, because it rules out half the model before I start. So this is record access, and the record's sharing detail will name the reason — role hierarchy, a rule, a team, or a manual share. Querying OpportunityShare for that record and those two users gives me the same answer without clicking, and it tells me the RowCause, which is the part that matters: a cause of Manual means nobody designed this, somebody clicked Share once, and that is worth saying out loud rather than replicating for the second user. Then I would check whether the requirement is this one record or the whole category, because a sharing rule for one record is a smell and a rule for a category is a design.

Ask before you answer
  • Are they in the same role, and is Grant Access Using Hierarchies on for this object?
  • Is the colleague seeing it through a sharing rule, an Opportunity team, or because they are the owner's manager?
Do not say this

Give the user the View All permission on Opportunity so they stop being blocked.

That fixes this record and every other Opportunity in the org at the same time, which is a much larger change than the ticket asked for. It also hides the real answer, which is that the two users differ somewhere, and that difference will produce the next ticket too.

Mid-level round

Mid-level Security & sharing questions

Security & sharing mid

How do you enforce field level security in Apex that feeds a Lightning component?

Testing Which of the available mechanisms you reach for, and whether you know why there are several.

mid

The component gets whatever the Apex method returns, so the check has to happen in Apex — the UI hiding a field on a layout is not security. My default is to run the query in user mode, with WITH USER_MODE on the SOQL, so a field the user cannot read never enters the result in the first place. When the records come from somewhere I do not control, I put Security.stripInaccessible(AccessType.READABLE, records) in front of the return and hand back getRecords(). For writes it is the same idea from the other direction: stripInaccessible with UPDATABLE stops a crafted payload from setting a field the user has no business setting.

senior

There are several mechanisms because they answer different questions, and picking by habit is how gaps appear. WITH USER_MODE is the cleanest when I own the query, because enforcement happens at the source and there is nothing to forget. stripInaccessible is for records I did not query myself, and it is also the one that protects writes — that direction is the one people skip, and it is the one that matters most, since a component posting a JSON blob into an @AuraEnabled method can name any field on the object. Describe calls like isAccessible() still have a place for a decision rather than a filter, but hand-rolling a loop over describes is where bugs live. What I would say plainly is that none of this is optional for anything that ships outside the org: a security review will fail an @AuraEnabled method that returns records straight out of a system mode query, and it is right to.

Security & sharing mid scenario

A customer portal user can see Cases that are not theirs. Where do you look?

Testing Whether external access is a separate model in your head, or the same one with different users in it.

What they tell you

An Experience Cloud site for customers. A user reports seeing Cases raised by another company in a list view. Internal Case org-wide default is Private, and there is a sharing rule opening Cases to a public group called Support Team.

mid

External access has its own org-wide default, separate from the internal one, and it is the first thing I would check — an internal Private with an external default of Public Read Only does exactly what is being reported. After that, the public group in the sharing rule, because a group defined as "all internal users" is not always what someone selected, and portal roles can end up inside a group by accident. Querying CaseShare for one of the leaked Cases and reading the RowCause names the reason outright, which beats reasoning about it from the configuration.

senior

I would treat this as an incident before treating it as a bug, because a customer has already seen another customer's data and the containment question comes before the root cause. So: how many records, which users, for how long, and can the site be restricted while it is investigated. Then the cause, and the external org-wide default is the usual answer — it is a separate setting from the internal one, it is easy to leave open while the internal model is tightened, and nothing about the internal configuration hints at it. A sharing set is the mechanism that should be granting portal access, scoped through Contact.AccountId to the user's own Account, and if there is no sharing set here then whatever access exists is coming from somewhere that was not designed for external users. The lasting fix is less about this one setting and more about the fact that nothing tested it: a permission test that logs in as a portal user and asserts a count is cheap, and this class of bug is invisible to every other kind of test.

Ask before you answer
  • Is the external org-wide default for Case set separately, and what is it set to?
  • Does the public group in that sharing rule contain a role that portal users roll up to?
Do not say this

Remove the Support Team sharing rule, since that is the only thing granting extra access.

That takes access away from the internal team who need it, to fix a problem affecting external users, without establishing that the rule was the cause. Public groups can pick up portal roles in ways nobody intended, but so can the external default, and deleting the visible thing first is how you end up with two incidents.

Security & sharing mid

Sharing rule, manual share, or Apex share — how do you pick?

Testing The difference between a rule that describes a policy and a share that records one decision.

mid

A sharing rule is right when the requirement is a category — every record owned by this role goes to that group — because it is declarative, recalculates itself when membership changes, and someone can read it in Setup a year later. A manual share is one decision about one record, so it is right for a genuine exception and wrong as a pattern, since nothing records why. Apex sharing is for the case a rule cannot express: access that depends on a field value, or on a relationship the sharing model has no concept of — and there it is my code writing rows into OpportunityShare and owning them from then on.

senior

The lifetime is what decides it for me, more than the expressiveness. A manual share on Manual row cause is deleted when the record's owner changes, which is exactly the behaviour you want for a favour and exactly the wrong behaviour for anything a process depends on — I have watched access vanish across an org because a territory realignment reassigned owners overnight. An Apex managed share with its own sharing reason survives an owner change, which is why anything programmatic should declare a reason rather than write Manual rows. And I would resist Apex sharing where a rule would do, because the code becomes the only documentation: nothing in Setup will tell the next person why 40,000 shares exist, and recalculating them after a change is a job somebody has to write.

Deep dive on this
Security & sharing mid

What does with sharing actually enforce?

Testing That you know the answer is narrower than the keyword suggests. This is where a lot of Apex is quietly insecure.

mid

Record access, and nothing else. with sharing makes a SOQL query respect who can see which rows, and it does not check object permissions or field level security at all — a class declared with sharing will happily return a field the running user cannot read, and happily update an object they have no edit access to. That gap is the one I check for in review, because the keyword reads like it covers everything. The fix is either Security.stripInaccessible before returning data, or running the query and the DML in user mode so all three are enforced together.

senior

The keyword is only half the story; the other half is what happens when you leave it off. A class with no sharing declaration runs in system mode for records — except when it is called from a class declared with sharing, where it inherits that caller's context, which means the same helper class is secure or not depending on who invoked it. inherited sharing exists to make that explicit rather than accidental, and it is what I want on a utility class. For anything new I would rather not be reasoning about keywords at all: WITH USER_MODE on the query and AccessLevel.USER_MODE on the DML enforce sharing, object permissions and field level security in one place, and they fail loudly instead of quietly returning too much. The one thing to be deliberate about is that user mode is genuinely restrictive, so batch and integration code that legitimately needs to see everything should say WITH SYSTEM_MODE on purpose rather than get it by omission.

Deep dive on this
Senior round

Senior Security & sharing questions

Security & sharing senior

When would you write Apex managed sharing, and what does it cost you?

Testing How you weigh a mechanism that can express anything against one nobody can read in Setup.

mid

When access depends on something the sharing model cannot see. A rule can target owners, roles and groups; it cannot say "the two people named on this record's fields, plus their managers, until the close date passes". For that I insert share rows — AccountShare, or MyObject__Share for a custom object — with the user or group Id, the access level, and a sharing reason I declared on the object. The cost is that it is now code: nothing in Setup explains it, and recalculating after a rule change is something I have to write.

senior

I reach for it late, because it is the only mechanism with no declarative trace. Two things make it work when it is genuinely needed. The sharing reason matters more than people expect — a row written with the Manual cause is deleted when the owner changes, so a process built on manual rows silently loses access during any reassignment, where a custom reason survives and can be recalculated as a set. And the code has to be idempotent and bulk safe, because it will run in a trigger during a data load: inserting duplicate shares throws, so I query what exists and insert the difference, with Database.insert(shares, false) so one bad row does not take the batch down. What I hold onto is that share rows are records with a storage and recalculation cost, so a design that produces several million of them is a design I would revisit before writing — sometimes the right answer is a different ownership model rather than more shares.

Deep dive on this
Security & sharing senior scenario

An audit flags that the integration user is a System Administrator. How do you fix it without breaking the integration?

Testing Willingness to reduce risk in stages, rather than proposing the ideal state and stalling.

What they tell you

One user account, System Administrator profile, used by four integrations through the same connected app. Nobody knows which objects each integration touches. The credentials are in a config file on a middleware server.

mid

One account for four integrations is the first thing to fix, because until they are separated nothing can be scoped — I cannot tell whose call failed, and revoking anything affects all four. So: a user per integration, its own connected app, and the old account kept alive while they move across one at a time. Then scope each new user from evidence rather than documentation: LoginHistory and the API event logs show which objects each one really calls, and that list is the requirement rather than whatever the profile currently allows. Where an integration only reads, it starts read-only.

senior

I would sequence this so every step reduces risk and none of them can break four things at once. First, split the identity: one user and one connected app per integration, moved over one at a time, which costs nothing in access terms and immediately makes every later step reversible and attributable. Second, discover rather than assume — event monitoring or the LoginHistory and API event logs tell me which objects each one really touches, and that list is almost always shorter than the profile allows and different from what the documentation says. Third, run each new user with the reduced permission set in a sandbox against a replay of real calls, because that is where the surprise field access shows up. Only then swap production, one integration at a time, with the old account still enabled so a rollback is a config change and not an incident. Somewhere in there I would move the credentials out of a file on a server into a JWT flow with a certificate, since a password sitting in config is the same class of finding and it is cheaper to fix while everything else is already being touched.

Ask before you answer
  • Do the four integrations share one connected app, or can they be separated into their own?
  • Is there API event monitoring or a log that shows which objects each one actually calls?
Do not say this

Build a permission set from the four integrations' documentation and swap the profile over in one change.

The documentation will be incomplete — that is why nobody knows what they touch. A single swap fails partially and silently: some calls keep working, some start returning insufficient access, and the failures show up as missing data days later rather than as an alert.

Security & sharing senior

How do you apply least privilege in an org that already has Modify All Data everywhere?

Testing Which trade-offs you name. Anyone can say least privilege; the question is what you do on the Monday.

mid

I would start by finding out who actually has it, because the answer is usually more people than anyone thinks. A query against PermissionSetAssignment joined to the sets carrying PermissionsModifyAllData gives me the real list in one go, including the sets granted for one incident three years ago and never removed. Then I would separate the cases: humans who were given it to get past a blocker, and integration users who were given it because nobody scoped the integration. The second group is easier and higher value, since an integration's real needs are a short list you can read off its code.

senior

The mistake is treating it as a project to revoke permissions, because that ends in a week of broken processes and a rollback. What works is doing it per consumer, smallest blast radius first. Integration users go first: each one gets its own user, its own permission set scoped to the objects and fields its code touches, and no administrative permissions — that is a contained change, and it converts a total compromise into a limited one. Then the human side, where the honest constraint is that you cannot remove an administrator's access without giving them another way to do the thing they needed it for, so every revocation has to be paired with a tool or a process. I would also be clear about what least privilege does not fix: a user with legitimate read access to 200,000 records can still export all of them, so the control for that is monitoring and event logs rather than permissions. And I would write down the exceptions rather than pretend there are none, because an undocumented exception becomes the norm.

Security & sharing senior scenario

Tightening an org-wide default cost a team their records overnight. What went wrong and what do you do?

Testing That an org-wide default change is a migration, not a checkbox.

What they tell you

Opportunity was Public Read Write and was changed to Private on Friday evening to satisfy an audit finding. On Monday a support team of 40 people cannot see the Opportunities they work on, and a nightly Apex job that updates them is now skipping most records.

mid

The support team had access from the default, so removing it removed theirs — nothing granted it to them explicitly, and that is the gap. The fast fix is a sharing rule opening Opportunity to the group they belong to, at Read Write, which restores Monday without reverting the audit change. The nightly job is a separate problem with the same cause: if that class is with sharing and runs as a user with no access to the records, it is now correctly seeing nothing.

senior

The lesson and the fix are different things, so I would do the fix first. A sharing rule to the support team's public group restores them in minutes and leaves the tighter default in place, which is the outcome both the audit and the team need. The nightly job I would look at carefully rather than patch: if it runs with sharing as a named user then it was relying on the default too, and the right answer is either an explicit share or a deliberate decision to run it in system mode with that written down — not quietly removing the keyword, which is the same access, undocumented. What actually went wrong is the process. An org-wide default change alters access for every user and recalculates sharing across the object, so it belongs in a window with a list of who currently sees these records and why, and a rule prepared in advance for each group that would lose access. Friday evening was the right time of week and the wrong amount of preparation.

Ask before you answer
  • Was the support team getting access through the old default, or through a rule that still exists?
  • Is the nightly job running as a specific user, and is its class declared with sharing?
Do not say this

Set Opportunity back to Public Read Write to restore Monday, then work out the sharing rules afterwards.

Reverting recalculates sharing across the whole object a second time, and on a large org that is hours during business use. It also puts the audit finding straight back. A targeted sharing rule restores the 40 people in minutes without moving the floor for everyone.

Security & sharing senior

What makes sharing expensive in a large org?

Testing Where you have seen the model cost real time, rather than which settings exist.

mid

Volume of share rows and how often they have to be rebuilt. Every grant is a row, so a rule that opens 2,000,000 records to a group of 300 people is a lot of rows to maintain, and anything that changes group membership — a role move, a new public group, a reassignment — makes Salesforce recalculate. That recalculation is why a role change on a big org is not instant, and why running a large data load and a hierarchy change on the same evening is a bad plan.

senior

Two things do most of the damage, and they are both distribution problems rather than settings problems. Ownership skew is the first: when one user owns tens of thousands of records on an object, every sharing recalculation for that user touches all of them, and any update to those records contends on the same rows — the integration user who owns 400,000 unassigned Leads is the classic version. Account data skew is the second, where hundreds of thousands of children hang off one parent, so a change to that parent's access cascades down all of them at once. The mitigations are unglamorous: spread ownership across several users even if the business does not care who owns unassigned records, keep the role hierarchy shallow where the objects are large, and use deferred sharing calculation for a planned migration so the recalculation happens once at the end instead of continuously through the load. What I would not do is treat this as something to tune later, because the fix after the fact is a data migration.

Deep dive on this