Access Control and Data Security

Sharing Model Declarations

All Apex classes that perform DML or SOQL operations must declare a sharing model.

Default Requirement:

All Apex classes must use with sharing by default. This enforces sharing rules and ensures proper security.

Exception for without sharing:

without sharing may only be used when:

  1. Reasoning is provided - Clear explanation of why sharing rules must be bypassed
  2. Prior approval is obtained - Team lead or architect approval
  3. It is documented - Inline comments and class documentation explain the exception
  4. Only the specific piece is isolated - Only the specific operation that requires without sharing should be in that class, not the surrounding logic

Example - Isolating without sharing:

If you need to query records without sharing rules, create a separate utility class for only that query:

// Bad: Entire service class without sharing
public without sharing class ACME_AccountService {
    public static List<Account> getAccounts() {
        // Query without sharing
    }

    public static void processAccounts(List<Account> accounts) {
        // Processing logic - doesn't need without sharing
    }
}

// Good: Isolated query class
public without sharing class ACME_AccountQueryService {
    /**
     * @description Queries accounts without sharing rules
     * @reason Required to access accounts for system-level processing
     * @approval Team Lead - 2024-01-01
     */
    public static List<Account> getAccountsWithoutSharing() {
        return [SELECT Id, Name FROM Account];
    }
}

// Main service class uses with sharing
public with sharing class ACME_AccountService {
    public static void processAccounts() {
        // Get accounts without sharing (isolated operation)
        List<Account> accounts = ACME_AccountQueryService.getAccountsWithoutSharing();

        // Process accounts (with sharing enforced)
        processAccountList(accounts);
    }

    private static void processAccountList(List<Account> accounts) {
        // Processing logic with sharing enforced
    }
}

Input Validation

Always validate input parameters before processing. Validate early, fail fast, and keep query shape constrained to known-safe fields/conditions.

public static ACME_Response processRecord(Id recordId) {
    if (recordId == null) {
        throw new ACME_ValidationException('Record ID is required');
    }
    // Process record
}

SOQL Injection Prevention

Never concatenate user input directly into SOQL queries. Always use bind variables.

Bad Example:

String query = 'SELECT Id FROM Account WHERE Name = \'' + userInput + '\'';

Good Example:

String safeInput = String.escapeSingleQuotes(userInput);
String query = 'SELECT Id FROM Account WHERE Name = \'' + safeInput + '\'';
List<Account> accounts = Database.query(query);

CRUD/FLS Enforcement

Always enforce CRUD/FLS permissions before performing SOQL or DML operations.

Default standard

Use Security.stripInaccessible() by default for CRUD/FLS enforcement. Centralize SOQL in selector classes (for example ACME_AccountSelector) and apply stripInaccessible() on the results before use or serialization.

// Selector class returns rows; strip fields the user cannot read
List<Account> accounts = ACME_AccountSelector.selectByIds(accountIds);
List<Account> accessibleAccounts = Security.stripInaccessible(
    AccessType.READABLE,
    accounts
).getRecords();

// Manual usage after inline SOQL
List<Account> queried = [SELECT Id, Name FROM Account WHERE Id IN :accountIds];
List<Account> accessibleQueried = Security.stripInaccessible(
    AccessType.READABLE,
    queried
).getRecords();

Accepted alternatives (when justified)

Security.stripInaccessible() remains the default. The alternatives below are acceptable when they better match the use case and the choice is documented in code comments.

1. SOQL with WITH SECURITY_ENFORCED

Use when you need queried data for downstream logic and processing and want field- and object-level security enforced at query time (for example, the query fails if the user cannot access a selected field). See Salesforce's topic Enforce Security for SOQL Queries.

List<Account> accounts = [
    SELECT Id, Name, Industry
    FROM Account
    WHERE Id IN :accountIds
    WITH SECURITY_ENFORCED
];
2. User mode access

Running database operations in user mode is allowed; see Enforce User Mode for Database Operations. It is an acceptable pattern but not the default preference in these standards unless user mode clearly fits the use case.

List<Account> accounts = [
    SELECT Id, Name
    FROM Account
    WHERE Id IN :accountIds
    WITH USER_MODE
];
3. Schema checks
// Check object access
if (!Schema.sObjectType.Account.isAccessible()) {
    throw new ACME_SecurityException('No access to Account object');
}

// Check field access
if (!Schema.sObjectType.Account.fields.Name.isAccessible()) {
    throw new ACME_SecurityException('No access to Account.Name field');
}
4. Manual field filtering
// Manually filter fields based on FLS
List<String> accessibleFields = new List<String>();
for (String field : requestedFields) {
    if (Schema.sObjectType.Account.fields.getMap().get(field).getDescribe().isAccessible()) {
        accessibleFields.add(field);
    }
}

Change History

Version 1.0 - 2026-03-26

  • Added initial version history tracking for this document.

Last Updated: 2026-03-26 Version: 1.0