10.1 Sharing, FLS, CRUD & Injection Prevention

Key Takeaways

  • with sharing enforces the running user's record-level sharing; without sharing ignores it; inherited sharing adopts the caller's class mode (default-safe for shared libraries)
  • CRUD/FLS are not automatic in Apex—use USER_MODE, WITH SECURITY_ENFORCED, Security.stripInaccessible, or explicit Schema describe isAccessible/isCreateable/isUpdateable/isDeletable checks
  • stripInaccessible removes fields the user cannot access from query results or DML-bound sObjects before you process or write them
  • Prevent SOQL injection with bind variables (:variable), never string-concatenate untrusted input into dynamic queries
  • Prevent XSS in Visualforce with HTMLENCODE, JSENCODE, JSINHTMLENCODE, and default merge-field escaping; understand CSRF protection for state-changing requests
Last updated: August 2026

10.1 Sharing, FLS, CRUD & Injection Prevention

Quick Answer: Apex runs in system mode by default for object/field permissions unless you opt into user-mode enforcement. Control record sharing with with sharing, without sharing, or inherited sharing. Enforce CRUD/FLS via USER_MODE, WITH SECURITY_ENFORCED, Security.stripInaccessible, or Schema describe checks. Stop SOQL injection with bind variables and XSS with Visualforce encode functions.

Platform Developer I treats security as a code-reading skill: you must recognize which keyword or API actually enforces what, and which “secure-looking” patterns still leave holes. Sharing, CRUD, and FLS are different layers—mixing them up is a common exam trap.

Three Layers of Data Security (Know the Difference)

LayerWhat it controlsHow Apex interacts
Object (CRUD)Create, Read, Update, Delete on an object typeNot automatic in classic system-mode Apex
Field (FLS)Read/edit on individual fieldsNot automatic in classic system-mode Apex
Record (sharing)Which rows the user may see/editControlled by class sharing keywords

A class declared with sharing still bypasses CRUD/FLS unless you add USER_MODE, SECURITY_ENFORCED, stripInaccessible, or manual describe checks. Conversely, USER_MODE does not replace the need to understand sharing keywords for legacy system-mode paths.

Sharing Keywords on Apex Classes

with sharing

public with sharing class AccountService {
    public static List<Account> getAccounts() {
        return [SELECT Id, Name FROM Account LIMIT 200];
    }
}

SOQL and DML respect the running user's sharing rules (OWD, role hierarchy, sharing rules, manual shares, teams, etc.). Use for user-facing controllers and services that must never leak private records.

without sharing

public without sharing class ComplianceRollupService {
    public static void recomputeAll() {
        // Intentionally sees records the interactive user might not own
        List<Account> all = [SELECT Id, AnnualRevenue FROM Account];
        // ... aggregate and write summary custom object the user can see
    }
}

Ignores end-user record sharing so elevated operations can see the full dataset. Does not grant object/field permissions—it is not a CRUD/FLS free pass for permissions you never checked. Use sparingly and document why system-wide access is required (batch rollups, integration users acting for the org, etc.).

inherited sharing

public inherited sharing class SharedUtility {
    public static List<Contact> queryContacts(Id accountId) {
        return [
            SELECT Id, Name, Email
            FROM Contact
            WHERE AccountId = :accountId
        ];
    }
}

The class inherits the sharing mode of the caller:

  • Called from a with sharing class → behaves as with sharing
  • Called from a without sharing class → behaves as without sharing
  • Entry points with no explicit mode (some default system contexts) → treated carefully; Salesforce documents that inherited sharing defaults to enforcing sharing when the class is used as an entry point in many scenarios—know the intent: libraries should not silently escalate

Exam heuristic: Prefer inherited sharing for reusable utilities so a secure caller stays secure. Prefer explicit with sharing for Visualforce/Lightning controllers. Prefer without sharing only when the requirement is org-wide data access that the user must not have interactively.

Inner classes inherit the outer class’s sharing declaration unless they declare their own.

Enforcing CRUD and FLS

1. USER_MODE (modern default for new code)

// SOQL in user mode
List<Account> accounts = [
    SELECT Id, Name, AnnualRevenue
    FROM Account
    WITH USER_MODE
    LIMIT 100
];

// DML in user mode
Database.insert(newAccount, AccessLevel.USER_MODE);
Database.update(accounts, AccessLevel.USER_MODE);

USER_MODE applies the running user’s object and field permissions (and related security rules for that mode). Operations fail or strip access according to the API when the user lacks rights—ideal for interactive UI paths.

System mode (historical default) is still available as AccessLevel.SYSTEM_MODE / WITH SYSTEM_MODE when a trusted automation must run elevated—pair it with deliberate design, not as a lazy default.

2. WITH SECURITY_ENFORCED (SOQL only)

List<Contact> contacts = [
    SELECT Id, Name, Email, SSN__c
    FROM Contact
    WITH SECURITY_ENFORCED
    LIMIT 50
];

If the user lacks FLS or object read on any field/object in the query, the platform throws a System.QueryException rather than returning partial data. Excellent for “fail closed” reads. It does not replace DML security by itself—pair with stripInaccessible, USER_MODE DML, or describe checks for writes.

3. Security.stripInaccessible

List<Account> queried = [
    SELECT Id, Name, AnnualRevenue, Restricted_Notes__c
    FROM Account
    LIMIT 200
];

SObjectAccessDecision decision = Security.stripInaccessible(
    AccessType.READABLE,
    queried
);
List<Account> safe = decision.getRecords();
// Fields the user cannot read are removed from the in-memory sObjects

Common AccessType values:

AccessTypeUse when
READABLECleaning query results before sending to UI or external consumers
CREATABLEBefore insert—drop fields user cannot create
UPDATABLEBefore update—drop fields user cannot edit
UPSERTABLEBefore upsert paths

stripInaccessible does not remove whole records the user cannot see under sharing; it sanitizes fields on the sObjects you already retrieved. Still use sharing keywords or user-mode queries appropriately for row visibility.

// DML pattern: strip then write
SObjectAccessDecision writeDecision = Security.stripInaccessible(
    AccessType.CREATABLE,
    new List<Account>{ draftAccount }
);
insert writeDecision.getRecords();

4. Explicit Schema describe checks

if (!Schema.sObjectType.Account.isCreateable()) {
    throw new AuraHandledException('No permission to create Accounts');
}
if (!Schema.sObjectType.Account.fields.AnnualRevenue.isUpdateable()) {
    throw new AuraHandledException('No permission to edit Annual Revenue');
}

Manual checks remain valid and appear often on older exam items. They scale poorly if copy-pasted field-by-field without helpers—but recognition of isAccessible, isCreateable, isUpdateable, isDeletable (object and field) is required.

SOQL Injection Prevention

Dynamic SOQL built from user input is a classic vulnerability:

// INSECURE — never do this
String name = userInput; // e.g. "x' OR Name != '"
String q = 'SELECT Id FROM Account WHERE Name = \'' + name + '\'';
List<Account> rows = Database.query(q);

A crafted string can change the WHERE clause logic and expose data.

Safe pattern — bind variables:

String name = userInput;
List<Account> rows = [
    SELECT Id, Name FROM Account WHERE Name = :name
];
// or dynamic SOQL with binds
List<Account> rows2 = Database.query(
    'SELECT Id, Name FROM Account WHERE Name = :name'
);

Bind variables are treated as values, not query text—injection of extra clauses fails. When you must accept field/object names dynamically, whitelist against Schema describe maps rather than concatenating free-form identifiers.

Map<String, Schema.SObjectField> fieldMap =
    Schema.SObjectType.Account.fields.getMap();
if (!fieldMap.containsKey(requestedField)) {
    throw new IllegalArgumentException('Invalid field');
}
// only then use the validated API name in dynamic SOQL

String.escapeSingleQuotes reduces quote-based breakage but is not a complete substitute for binds and whitelisting on the exam’s preferred answers.

XSS Prevention in Visualforce

Cross-site scripting injects attacker script into pages viewed by other users. In Visualforce:

DefenseRole
Default merge-field escapingMany {!...} outputs are HTML-escaped by default
HTMLENCODEEncode for HTML body context
JSENCODEEncode for JavaScript string context
JSINHTMLENCODEEncode when JS appears inside HTML attributes/context
URLENCODEEncode for URL query components
Avoid escape="false"Disabling escape on apex:outputText is a red flag unless content is trusted and required
<!-- Prefer encoding when rendering untrusted data into scripts -->
<script>
  var name = '{!JSENCODE(Account.Name)}';
</script>

<apex:outputText value="{!HTMLENCODE(userComment)}" escape="false" />
<!-- Better: leave escape true and avoid raw HTML unless necessary -->

If a snippet shows user input rendered with escape="false" and no encode function, mark it insecure.

CSRF Basics

Cross-Site Request Forgery tricks a logged-in browser into submitting a state-changing request the user did not intend. Salesforce Visualforce and many platform endpoints include CSRF tokens on forms and require proper request methods for mutations. Exam awareness:

  • Prefer framework form posts over ad-hoc GETs that mutate data
  • Do not disable CSRF protections
  • Custom HTTP endpoints and sites need explicit anti-CSRF design

You rarely write the token by hand; you must recognize that ignoring CSRF or using GET for deletes/updates is unsafe.

Exam Recognition of Insecure Snippets

When the question shows code, scan in this order:

  1. Sharing — Does a user-facing class omit with sharing / use without sharing without justification?
  2. CRUD/FLS — System-mode query of sensitive fields with no USER_MODE, SECURITY_ENFORCED, stripInaccessible, or describe checks?
  3. Injection — String concatenation into Database.query or dynamic SOSL?
  4. XSS — Unescaped output, escape="false", user data inside <script> without JSENCODE?
  5. False confidencewith sharing alone does not equal full CRUD/FLS compliance.
// Classic trap: looks careful, still system-mode FLS
public with sharing class ProfileController {
    @AuraEnabled
    public static Account getAccount(Id accountId) {
        return [
            SELECT Id, Name, Restricted_Field__c
            FROM Account
            WHERE Id = :accountId
        ]; // sharing OK; FLS not enforced
    }
}

Hardening options: add WITH USER_MODE / WITH SECURITY_ENFORCED, or strip inaccessible fields before return.

Putting It Together

Secure Apex is layered: sharing keywords for rows, USER_MODE / SECURITY_ENFORCED / stripInaccessible / describe for objects and fields, binds and whitelists against injection, and encode/escape against XSS—plus platform CSRF defaults for forms. On the exam, match the vulnerability in the stem to the correct control; do not pick “with sharing” as a universal answer for FLS questions.

Test Your Knowledge

A Visualforce controller class is declared without a sharing keyword and queries Account records for display. Which statement is accurate?

A
B
C
D
Test Your Knowledge

Which approach best prevents SOQL injection when filtering Accounts by a name string supplied from the UI?

A
B
C
D
Test Your Knowledge

A developer queries several Account fields in system mode and must remove any fields the running user cannot read before returning data to a Lightning component. Which API fits?

A
B
C
D