Enforce Security with the stripInaccessible Method
The Security.stripInaccessible() method takes a permission set ID as a parameter and enforces field-level and object-level access as per the specified permission set, in addition to the running user’s permissions.
The method allows graceful degradation of the application by omitting fields, rather than failing outright, and is similar to the behavior with views, reports, and layouts. Developers can enforce security at the level of business processes, and not merely at the object, field, or row level. This approach allows coverage of a large number of platform features that pass records into Apex from potentially untrusted sources such as Apex REST, Lightning clients, and so on.
Implementation Details
The field-level and object-level data protection is accessed through the Security and SObjectAccessDecision classes. The access check is based on the field-level permission of the current user in the context of the specified operation—create, read, update, or upsert. The Security.stripInaccessible() method checks the source records for fields that don’t meet the field-level security check for the current user. The method also checks the source records for lookup or master-detail relationship fields to which the current user doesn’t have access. The method creates a return list of sObjects that is identical to the source records, except that the fields that are inaccessible to the current user are removed. The sObjects returned by the getRecords method contain records in the same order as the sObjects in the sourceRecords parameter of the stripInaccessible method.
Considerations
- Use this feature for graceful degradation on errors by omitting fields, rather than failing outright.
- The stripInaccessible method doesn’t support AggregateResult SObject. If the source records are of AggregateResult SObject type, an exception is thrown.
- To enforce object and field permissions on the User object and hide a user’s personal information from other users in orgs with Experience Cloud sites, see Enforcing Object and Field Permissions.
Examples
These examples show several applications of the stripInaccessible method.
1SObjectAccessDecision securityDecision =
2 Security.stripInaccessible(AccessType.READABLE,
3 [SELECT Name, BudgetedCost, ActualCost FROM Campaign] );
4
5 // Construct the output table
6 if (securityDecision.getRemovedFields().get('Campaign').contains('ActualCost')) {
7 for (Campaign c : securityDecision.getRecords()) {
8 //System.debug Output: Name, BudgetedCost
9 }
10 } else {
11 for (Campaign c : securityDecision.getRecords()) {
12 //System.debug Output: Name, BudgetedCost, ActualCost
13 }
14}1List<Account> accountsWithContacts =
2 [SELECT Id, Name, Phone,
3 (SELECT Id, LastName, Phone FROM Account.Contacts)
4 FROM Account];
5
6 // Strip fields that are not readable
7 SObjectAccessDecision decision = Security.stripInaccessible(
8 AccessType.READABLE,
9 accountsWithContacts);
10
11// Print stripped records
12 for (Integer i = 0; i < accountsWithContacts.size(); i++)
13 {
14 System.debug('Insecure record access: '+accountsWithContacts[i]);
15 System.debug('Secure record access: '+decision.getRecords()[i]);
16 }
17
18// Print modified indexes
19 System.debug('Records modified by stripInaccessible: '+decision.getModifiedIndexes());
20
21// Print removed fields
22 System.debug('Fields removed by stripInaccessible: '+decision.getRemovedFields());1List<Account> newAccounts = new List<Account>();
2Account a = new Account(Name='Acme Corporation');
3Account b = new Account(Name='Blaze Comics', Rating=’Warm’);
4newAccounts.add(a);
5newAccounts.add(b);
6
7SObjectAccessDecision securityDecision = Security.stripInaccessible(
8 AccessType.CREATABLE, newAccounts);
9
10// No exceptions are thrown and no rating is set
11insert securityDecision.getRecords();
12
13System.debug(securityDecision.getRemovedFields().get('Account')); // Prints "Rating"
14System.debug(securityDecision.getModifiedIndexes()); // Prints "1"1String jsonInput =
2'[' +
3'{' +
4'"Name": "InGen",' +
5'"AnnualRevenue": "100"' +
6'},' +
7'{' +
8'"Name": "Octan"' +
9'}' +
10']';
11
12List<Account> accounts = (List<Account>)JSON.deserializeStrict(jsonInput, List<Account>.class);
13SObjectAccessDecision securityDecision = Security.stripInaccessible(
14 AccessType.UPDATABLE, accounts);
15
16// Secure update
17update securityDecision.getRecords(); // Doesn’t update AnnualRevenue field
18System.debug(String.join(securityDecision.getRemovedFields().get('Account'), ', ')); // Prints "AnnualRevenue"
19System.debug(String.join(securityDecision.getModifiedIndexes(), ', ')); // Prints "0”1// Account__c is a lookup from MyCustomObject__c to Account
2@IsTest
3 public class TestCustomObjectLookupStripped {
4 @IsTest static void caseCustomObjectStripped() {
5 Account a = new Account(Name='foo');
6 insert a;
7 List<MyCustomObject__c> records = new List<MyCustomObject__c>{
8 new MyCustomObject__c(Name='Custom0', Account__c=a.id)
9 };
10 insert records;
11 records = [SELECT Id, Account__c FROM MyCustomObject__c];
12 SObjectAccessDecision securityDecision = Security.stripInaccessible
13 (AccessType.READABLE, records);
14
15 // Verify stripped records
16 System.assertEquals(1, securityDecision.getRecords().size());
17 for (SObject strippedRecord : securityDecision.getRecords()) {
18 System.debug('Id should be set as Id fields are ignored: ' +
19 strippedRecord.isSet('Id')); // prints true
20 System.debug('Lookup field FLS is not READABLE to running user,
21 should not be set: ' +
22 strippedRecord.isSet('Account__c')); // prints false
23 }
24 }
25 }Versioned Behavior Changes
In API version 67.0 and later, Apex runs in user context by default, meaning that the current user’s permissions and field-level security (FLS) are enforced during code execution. In API version 66.0 and earlier, system mode is the default.