This post is my attempt to reach out to Salesforce with a full narration of a bug in Security.stripInaccessible() method with protected custom settings only. Also this could help any ISV Partners using Security.stripInaccessible() with protected custom settings and running into issues.
Here is a quick summary of the issue:
- Create a Protected Custom setting either of Hierarchy or List Type
- As part of Security Review Compliance, one will either check for CRUD/FLS via Apex Describe Calls or can use the newly released Security.stripInaccessible() Apex utility.
- Add a class like Foo.cls that uses Security.stripInaccessible() to verify Custom Setting access in any Aura, LWC, Batch, or other Apex code.
- Do a managed release or beta package upload, i.e. with a package prefix
- Install the managed package in a client org.
- In the client org, invoke Foo.cls, and you will get a FALSE Exception from Security.stripInaccessible() despite having READ/WRITE access on the protected custom settings.
Please Note: This issue is not reproducible in the packaging org, it only comes in client/target org after installing the package.
The following video explains the situation
How to reproduce this error?
Install Managed Package
All the source code demonstrated in the above video can be installed via this package link:
https://login.salesforce.com/packaging/installPackage.apexp?p0=04t2x000003D7Rb&isdtp=p1
Or deploy this video’s source from Github to a new Developer Edition org, and upload a managed package as released/beta after registering a prefix. You can check out this repo, and use normal SFDX development tooling/flows to deploy this code to your own org.
Setup a Target Org
- Assign the Accessor permission set to get the required permissions on Classes and Custom Settings.
- Create a class with the following source code; in case you deployed the Github source to your own org, replace the “striptest” package prefix in the following code, with your own namespace prefix.
public class AccessorPlayGround {
public static void populate() {
//populate sample
datastriptest.Accessor.populateAll();
}
public static void assertAccess() {
// Generate classic CRUD FLS report
String accessReport = striptest.Accessor.generateClassicAccessReport();
System.debug (accessReport);
try {
striptest.Accessor.accessProtectedHierarchy(AccessType.READABLE);
System.debug (' ProtectedHierarchy is READABLE');
} catch (Exception ex) {
System.debug (' ProtectedHierarchy ' + ex.getMessage() + '\n' + ex.getStackTraceString());
}
try {
striptest.Accessor.accessProtectedList(AccessType.READABLE);
System.debug (' ProtectedList is READABLE');
} catch (Exception ex) {
System.debug (' ProtectedList ' + ex.getMessage() + '\n' + ex.getStackTraceString());
}
try {
striptest.Accessor.accessPublicHierarchy(AccessType.READABLE);
System.debug (' PublicHierarchy is READABLE');
} catch (Exception ex) {
System.debug (' PublicHierarchy ' + ex.getMessage() + '\n' + ex.getStackTraceString());
}
try {
striptest.Accessor.accessPublicList(AccessType.READABLE);
System.debug (' PublicList is READABLE');
} catch (Exception ex) {
System.debug (' PublicList ' + ex.getMessage() + '\n' + ex.getStackTraceString());
}
}
}
Execute the following code to start observing the debug logs for exception traces regarding Protected Custom Settings
AccessorPlayGround.assertAccess();
If you have configured permission sets correct, the user_debug output in debug logs for the above code line should have something to the following. Observe the bug in Security.stripInaccessible() around Protected Custom Settings only. This is a bug because the CRUD/FLS permissions are given for UPDATE to all 4 custom settings, only the public custom settings work correctly.
00:46:05.2 (147600827)|USER_DEBUG|[10]|DEBUG|Access Report for Object: striptest__Protected_Hierarchy__cObject : striptest__Protected_Hierarchy__c is Accessible: true, Createable: true, Updateable: trueField : Record ID is Accessible: true, Createable: false, Updateable: falseField : Deleted is Accessible: true, Createable: false, Updateable: falseField : Name is Accessible: true, Createable: true, Updateable: trueField : Location is Accessible: true, Createable: true, Updateable: trueField : Created Date is Accessible: true, Createable: true, Updateable: falseField : Created By ID is Accessible: true, Createable: true, Updateable: falseField : Last Modified Date is Accessible: true, Createable: true, Updateable: falseField : Last Modified By ID is Accessible: true, Createable: true, Updateable: falseField : System Modstamp is Accessible: true, Createable: false, Updateable: falseField : Last Viewed Date is Accessible: true, Createable: false, Updateable: falseField : Last Referenced Date is Accessible: true, Createable: false, Updateable: falseField : value is Accessible: true, Createable: true, Updateable: trueAccess Report for Object: striptest__Public_Hierarchy__cObject : striptest__Public_Hierarchy__c is Accessible: true, Createable: true, Updateable: trueField : Record ID is Accessible: true, Createable: false, Updateable: falseField : Deleted is Accessible: true, Createable: false, Updateable: falseField : Name is Accessible: true, Createable: true, Updateable: trueField : Location is Accessible: true, Createable: true, Updateable: trueField : Created Date is Accessible: true, Createable: true, Updateable: falseField : Created By ID is Accessible: true, Createable: true, Updateable: falseField : Last Modified Date is Accessible: true, Createable: true, Updateable: falseField : Last Modified By ID is Accessible: true, Createable: true, Updateable: falseField : System Modstamp is Accessible: true, Createable: false, Updateable: falseField : Last Viewed Date is Accessible: true, Createable: false, Updateable: falseField : Last Referenced Date is Accessible: true, Createable: false, Updateable: falseField : Value is Accessible: true, Createable: true, Updateable: trueAccess Report for Object: striptest__Protected_List__cObject : striptest__Protected_List__c is Accessible: true, Createable: true, Updateable: trueField : Record ID is Accessible: true, Createable: false, Updateable: falseField : Deleted is Accessible: true, Createable: false, Updateable: falseField : Name is Accessible: true, Createable: true, Updateable: trueField : Location is Accessible: true, Createable: true, Updateable: trueField : Created Date is Accessible: true, Createable: true, Updateable: falseField : Created By ID is Accessible: true, Createable: true, Updateable: falseField : Last Modified Date is Accessible: true, Createable: true, Updateable: falseField : Last Modified By ID is Accessible: true, Createable: true, Updateable: falseField : System Modstamp is Accessible: true, Createable: false, Updateable: falseField : Last Viewed Date is Accessible: true, Createable: false, Updateable: falseField : Last Referenced Date is Accessible: true, Createable: false, Updateable: falseField : Value is Accessible: true, Createable: true, Updateable: trueAccess Report for Object: striptest__Public_List__cObject : striptest__Public_List__c is Accessible: true, Createable: true, Updateable: trueField : Record ID is Accessible: true, Createable: false, Updateable: falseField : Deleted is Accessible: true, Createable: false, Updateable: falseField : Name is Accessible: true, Createable: true, Updateable: trueField : Location is Accessible: true, Createable: true, Updateable: trueField : Created Date is Accessible: true, Createable: true, Updateable: falseField : Created By ID is Accessible: true, Createable: true, Updateable: falseField : Last Modified Date is Accessible: true, Createable: true, Updateable: falseField : Last Modified By ID is Accessible: true, Createable: true, Updateable: falseField : System Modstamp is Accessible: true, Createable: false, Updateable: falseField : Last Viewed Date is Accessible: true, Createable: false, Updateable: falseField : Last Referenced Date is Accessible: true, Createable: false, Updateable: falseField : Value is Accessible: true, Createable: true, Updateable: true........00:46:05.2 (176462805)|USER_DEBUG|[16]|DEBUG| ProtectedHierarchy No access to entityClass.System.Security.stripInaccessible: line 15, column 1Class.System.Security.stripInaccessible: line 10, column 1Class.striptest.Accessor.assertAccess: line 177, column 1Class.striptest.Accessor.accessProtectedHierarchy: line 65, column 1Class.abhinav.AccessorPlayGround.assertAccess: line 13, column 1AnonymousBlock: line 1, column 1AnonymousBlock: line 1, column 1........00:46:05.2 (184833029)|USER_DEBUG|[23]|DEBUG| ProtectedList No access to entityClass.System.Security.stripInaccessible: line 15, column 1Class.System.Security.stripInaccessible: line 10, column 1Class.striptest.Accessor.assertAccess: line 177, column 1Class.striptest.Accessor.accessProtectedList: line 43, column 1Class.abhinav.AccessorPlayGround.assertAccess: line 20, column 1AnonymousBlock: line 1, column 1AnonymousBlock: line 1, column 1........00:46:05.2 (203847426)|USER_DEBUG|[28]|DEBUG| PublicHierarchy is READABLE........00:46:05.2 (215334265)|USER_DEBUG|[35]|DEBUG| PublicList is READABLE
Possible Workarounds?
Avoid Security.stripInaccessible() only for custom settings by checking it dynamically, as shown below
public static void assertAccess(AccessType accessType, SObject[] records) {
Schema.DescribeSObjectResult dsr = records[0].getSObjectType().getDescribe();
String objectName = dsr.getLabel();
if (dsr.isCustomSetting()) {
/*Security.stripInaccessible(...) doesnt works correctly with protected custom settings*/
Boolean hasAccess = false;
switch on accessType {
when READABLE {hasAccess = dsr.isAccessible();}
when CREATABLE {hasAccess = dsr.isCreateable();}
when UPDATABLE {hasAccess = dsr.isUpdateable();}
when UPSERTABLE {hasAccess = dsr.isUpdateable() && dsr.isCreateable();}
when else {hasAccess = false;}
}
if (hasAccess == false) {
String msg = String.format('"{0}" access missing on Object: "{1}"', new String[] { accessType.name(), objectName});
throw new YourException(msg);}
} else {
// Normal Objecttry {// Strip fields that are not updatableSObjectAccessDecision decision = Security.stripInaccessible(accessType, records);.. usual code
}
Managed Beta Upload Failure
With protected custom settings and this Github source, I ran into an issue of internal Salesforce error while uploading a Managed Beta package. I managed to get out of this issue, by doing a managed release upload. I know it might not be possible to always skip Beta packages. This issue happened with me and my peer developer as well, though after uploading a released package the beta packages are uploading as well 🙁
Update 24-July-2020
I shared the blog in a support case with Salesforce, and they acknowledged this problem as a known issue. If you are impacted by this issue as well, You can click on the button – “This issue affects me” on this article to get the latest updates on this issue.

References
- Managed Package Source Code: https://github.com/abhinavguptas/sf-managed-package-custom-setting-stripInaccessible
- YouTube Video Walkthrough: https://youtu.be/fKCly_WeBc8
- Update 24-July-2020: Salesforce reported this as a Known Issue.
Leave a Reply