The principle of least privilege (PoLP) is one of the most widely applied concepts in information security. As a Salesforce Architect, it is your responsibility to design data access controls that are aligned with the needs of the users while keeping the level of privileges to the minimum.
The Salesforce Platform provides a rich set of tools that you can use to ensure users and processes can access only the information and resources that are essential to perform their duties.
This post covers several aspects that should be considered when designing for PoLP:
- First, we will review the key platform capabilities that you can use to design tight data access controls. We will talk about user context vs. system context and other contexts of execution.
- Next, we will look at the Salesforce data access paradigm and what it means for data access controls.
- Finally, we will look at an example and the associated security risks, and discuss possible remediation approaches.
Salesforce data access controls are primarily managed at two levels:
- Profiles, permission sets, and permission set groups for defining the level of access to the objects (or tables) and their fields (or columns). It is a good idea to use permission sets and permission set groups over profiles when possible. See How to Build a User Security Model for more information about when to use which method.
- Sharing rules and sharing sets for defining access to specific records (or rows) for a given object. Record-based sharing is also delivered with other advanced capabilities such as groups, teams, territory, and Apex sharing.
Take the analogy of a car:
- The profiles and permission sets are your driver’s license. They specify what type of vehicle you can drive.
- Record-level access is provided by the car keys. To drive a specific car, the license is not enough. You still need its keys.
For most default components, both profiles/permission sets and record-level access rights are enforced. This is the case for standard Lightning components and the standard API, for example.
There are some cases where profiles/permission sets and record-level access rights are always bypassed, as with Process Builder.
And in other cases, the execution context varies!
For example, Apex by default runs with bypassing the object-level security, and field-level security. The only exceptions to this rule are Apex code that is executed with the
executeAnonymous call and Chatter in Apex. If no sharing keyword is specified, the sharing rules are ignored too, unless this class is an @AuraEnabled Apex controller.
Apex provides tools to assist with enforcing field and object level security in Apex code, like
with security_enforced, which means that a mixed mode of checking access for some, but not all access can be allowed. It is also recommended to specify sharing access with the keywords
without sharing, or
with inherited sharing.
You can find more information about these capabilities in the Apex Security and Sharing documentation.
Another example where the execution context varies is Flows. Here as well, it is possible to bypass sharing rules, object-level security, and field-level security and change the execution context of the flow.
As you can see, the Salesforce Platform provides many flexible options for the Salesforce Architect to control the execution context of their components. These options can be organized in four main categories:
- In the upper right cell, the code runs in system context.
- In the lower left cell, the code runs in user context. (This should be your default option.)
- Everything else sits in between.
When talking about user versus system context, make sure that you specify whether the context is related to object/field-level access, record-level access, or both. It is usually clearer to use the terms with/without bypassing permissions and with/without sharing.
Salesforce data access controls follow a simple paradigm: Data access for a given user is the same, no matter the channel used to access the data.
This paradigm is fine most of the time. When a user needs access to a given record, let’s just grant them access! But there are some cases where we want the user to get access to a record only under specific conditions. We might even want to perform an operation on their behalf without them accessing the record at all!
For example, a user that has write access to a shopping cart will be able to update it through the user interface, following a multistep shopping journey. But with write access to the cart, they will also be able to update it outside the guided flow, from the standard record page, or even directly via the API.
There are several channels that a user can use by default. The main ones are:
- Lightning and classic user interface – Standard and custom pages
- Communities – Standard dynamic (Record Detail, Record List, and Related Record List) and static record pages, as well as custom pages
- Reports and dashboards
- List views
- Global search
- APIs – including access through connected apps
- Exposed XHR (XML HTTP Request) endpoints that can be called directly from a browser. Note: Aura and Lightning Web Components are based on a SPA (Single Page Application) architecture and XHR is the way components transfer data between the client side and server side.
When profiles, permission sets, or sharing rules are too loose, users have more access than what is really required. Internal users may accidentally corrupt data. But the main risk is with external access: Attackers may exploit these vulnerabilities to access confidential and sensitive information (for example, download the full product price list) or to perform unauthorized operations (for example, change the price of a product they have added to their cart).
Let’s look at an example in more detail.
Here are the key steps of a typical purchase journey, with the data access controls and the associated access levels required for each object category. This example is purely hypothetical; its purpose is to illustrate the above risks. The access levels described in the table below are not a recommended best practice.
Please also note that, even though the example below is based on a B2C scenario, the risks are relevant to any on-platform application.
There are several ways to mitigate the above risks in alignment with the principle of least privilege.
Option 1: Bypass user permissions or run code without sharing
The best way is to prevent direct access to data by not granting object/field/record access. You can then bypass permissions or record-level access and programmatically define the access level.
With this approach, the developer is in full control and data access can be finely tuned to be aligned with PoLP.
This requires advanced skills to implement the right controls at the right time. If poorly implemented, the risk of a security breach could be even higher. When bypassing data access, the developer can grant access to any records from any objects from their code.
That’s why it is critical to perform thorough security testing at several levels:
- Security-oriented unit test: Run unit tests with test users that have various access rights (use runAs), and check that what they can see and do is aligned with what they are entitled to. Be sure to include specific tests that verify behavior for users without access.
- Functional tests: Run both positive and negative testing.
- Black-box and white-box penetration tests: Discover any residual gaps that haven’t been identified in the previous phases.
When it is not possible to bypass permissions or record-level access other options should then be considered.
Option 2: Block all channels
Prevent users from using any channels that would allow them to access data in an uncontrolled way, for example outside the guided flow.
Some channels can be easily blocked; it is possible to remove API access for a group of users, restrict list views, and remove access to reports. This, however, would dramatically limit the capabilities that would be otherwise available out-of-the-box for these users. For example, without API access, users may not be able to make use of some third-party apps. For external users, the Community user interface would have to be fully customized. And, some channels like global search cannot be completely deactivated.
Option 3: Use session-based permission sets
Session-based permission sets enable you to temporarily activate permissions granted to the user for the duration of the session.
This approach is a good option to elevate access to objects and fields for internal users. The impact on the user experience makes it difficult to use for externally facing applications, though. Plus, record-level access would still need to be addressed separately.
Option 4: Implement additional controls
Add automations such as validation rules, triggers, and so on to prevent unauthorized data changes.
While this approach is aligned with Salesforce data access principles, it can involve significant work to ensure that all negative use cases (where the user should not have access to the data) are appropriately covered. In addition, this may have a negative impact on the user experience, where the user tries to perform an operation, only to be told afterward that it cannot be completed. And, this wouldn’t prevent the user from having read access to the data.
There are many tools that Salesforce architects can employ to design a solution that is aligned with the PoLP. When specifying the security model, it is critical to understand the context in which the data should be accessed. The key questions to ask are:
- Is it appropriate for a user to have access to a record no matter which channel they go through? Should the record be accessed under specific conditions only?
- Is it acceptable for the user to actually have access to the record, or should the operation be performed on their behalf?
Also keep in mind that profiles and permission sets are also used to grant access to non-data related privileges, which are usually enforced in all components.
Sandrine Hiest is a Program Architect Director at Salesforce. Since joining in 2017, Sandrine has served as a trusted advisor, technical leader, and platform expert for the company’s most complex enterprise customers. Sandrine is passionate about empowering her customers and coaching other architects on security and scalability topics.