Skip to content

Programmatic Security: Apex Enforcement & Secure Coding

Programmatic security covers how developers enforce security in Apex code, prevent common vulnerabilities, and securely integrate with external systems. This is CTA Objective 2.4, and the board expects you to know when declarative security falls short and how to enforce security correctly in code.

The Sharing Keyword Spectrum

Every Apex class runs in one of three sharing contexts. Choosing the wrong one is a common security vulnerability.

with sharing / without sharing / inherited sharing

KeywordRecord AccessUse Case
with sharingEnforces the running user’s sharing rules (OWD, role hierarchy, sharing rules)Default for most classes; user-facing logic
without sharingIgnores all sharing rules; sees all recordsSystem operations, integrations, admin utilities
inherited sharingInherits the sharing context of the calling classUtility classes called from multiple contexts
(no keyword)Inherits sharing context from calling class; defaults to without sharing only when used as the entry point of an Apex transactionAvoid; explicit is always better
Decision tree for choosing with sharing, without sharing, or inherited sharing on an Apex class based on whether it is user-facing, a utility, or a system operation.
Figure 1. The sharing keyword decision defaults to with sharing for any user-facing class. inherited sharing is correct for utility classes that serve multiple callers, while without sharing requires explicit justification and narrow scope to avoid becoming a security bypass.

The “without sharing” Risk

Every class marked without sharing is a potential security bypass. The CTA board will question any architecture that uses without sharing broadly. Always document WHY a class needs to bypass sharing, and keep the scope narrow. Ideally, only a single inner class method performs the elevated query, not the entire outer class.

Sharing Context Interaction

How sharing context flows through an Apex call chain, showing that inherited sharing adopts the caller's context while without sharing overrides it regardless of caller.
Figure 2. Sharing context propagates through the call chain, but without sharing always overrides regardless of its caller’s context. This is why without sharing classes should be narrow (a single method rather than an entire service class) to limit the bypass scope.

inherited sharing Behavior

inherited sharing in the top-level context (e.g., a trigger or anonymous Apex) runs as with sharing. It only inherits a different context when called from another class. This makes it safe as a default for utility classes.

CRUD and FLS Enforcement

Sharing controls which records a user sees. CRUD and FLS control what operations they can perform and which fields they can access. Apex does NOT automatically enforce CRUD or FLS; developers must do it explicitly.

Enforcement Methods Comparison

MethodEnforcesAvailable SinceWhere It Works
WITH SECURITY_ENFORCEDObject CRUD + FLS (Read only)Spring ‘20 (GA)SOQL queries
WITH USER_MODECRUD + FLS + SharingSpring ‘23SOQL + DML
stripInaccessible()FLSSpring ‘20 (GA)Apex (before DML or after query)
Schema.describeManual checkAlwaysApex (programmatic checks)
Security.stripInaccessible()FLS per operation typeSpring ‘20 (GA)Apex

WITH SECURITY_ENFORCED

Appended to a SOQL query to enforce FLS on all referenced fields. If the running user lacks Read access to any field in the query, a System.QueryException is thrown.

// Enforces FLS - throws exception if user can't read any field
List<Account> accounts = [
SELECT Name, Phone, AnnualRevenue
FROM Account
WHERE Industry = 'Technology'
WITH SECURITY_ENFORCED
];

Limitations:

  • Read-only enforcement (does not apply to DML)
  • Throws an exception rather than stripping fields; all or nothing
  • Enforces both object-level CRUD and field-level security on SOQL SELECT queries
  • Does not enforce sharing (use with sharing for that)

WITH USER_MODE / WITH SYSTEM_MODE

The modern approach. Available on both SOQL and DML.

// USER_MODE: Enforces CRUD + FLS + sharing
List<Account> accounts = [
SELECT Name, Phone, AnnualRevenue
FROM Account
WHERE Industry = 'Technology'
WITH USER_MODE
];
// USER_MODE on DML: Enforces CRUD + FLS
Database.insert(newAccounts, AccessLevel.USER_MODE);
Database.update(existingAccounts, AccessLevel.USER_MODE);
// SYSTEM_MODE: Bypasses CRUD + FLS + sharing (explicit bypass)
List<Account> allAccounts = [
SELECT Name, Phone, AnnualRevenue
FROM Account
WITH SYSTEM_MODE
];

Behavior details:

  • Works on both SOQL and DML (SECURITY_ENFORCED is SOQL-only)
  • WITH USER_MODE in inline SOQL throws System.QueryException for inaccessible fields (similar to WITH SECURITY_ENFORCED)
  • Database.query with AccessLevel.USER_MODE may silently strip inaccessible fields rather than throwing
  • Use stripInaccessible() for consistent silent stripping across all contexts
  • Enforces sharing rules automatically

CTA Recommendation

For new development, recommend USER_MODE as the default for all SOQL and DML operations. It provides the broadest security enforcement with the least code. Reserve SYSTEM_MODE for system operations that explicitly need to bypass user permissions.

stripInaccessible()

Strips fields the running user cannot access before DML or after a query. Provides granular control over which operation type to check.

// Strip fields user can't read from query results
List<Account> accounts = [SELECT Name, Phone, AnnualRevenue FROM Account];
SObjectAccessDecision decision = Security.stripInaccessible(
AccessType.READABLE,
accounts
);
List<Account> sanitized = decision.getRecords();
// sanitized records have inaccessible fields removed
// Strip fields user can't create before insert
List<Account> newAccounts = new List<Account>();
// ... populate accounts
SObjectAccessDecision insertDecision = Security.stripInaccessible(
AccessType.CREATABLE,
newAccounts
);
insert insertDecision.getRecords();

AccessType options: READABLE, CREATABLE, UPDATABLE, UPSERTABLE

Enforcement Selection Guide

Decision tree for choosing between WITH USER_MODE, WITH SECURITY_ENFORCED, and stripInaccessible based on whether the operation is SOQL or DML and the desired failure behavior.
Figure 3. WITH USER_MODE is the modern default for both SOQL and DML, enforcing CRUD, FLS, and sharing in a single keyword. Use stripInaccessible() when silent field stripping is preferred over throwing exceptions, such as in bulk data operations.

Declarative Filtering: Restriction & Scoping Rules

While sharing rules grant access, these features filter access. They are declarative but directly impact how Apex queries return data.

Restriction Rules

Restriction Rules narrow the records a user can see, even if they have sharing access via OWD, role hierarchy, or sharing rules. Available on custom objects, external objects, contracts, events, tasks, time sheets, and time sheet entries, but not on Account, Contact, Opportunity, or Case.

AspectSharing RulesRestriction Rules
EffectGrants access (additive)Narrows access (subtractive)
PrecedenceEvaluated firstEvaluated after sharing
Use CaseOpen access to regional teamsHide sensitive/VIP records from all but a few
Limit300 ownership-based + 300 criteria-based per object2 per object (EE/DE) or 5 per object (PE/UE)

Restriction Rules & Apex

Restriction Rules are enforced when Apex uses user-mode database operations: SOQL with WITH USER_MODE or DML with AccessLevel.USER_MODE. The with sharing keyword enforces sharing rules but does not enforce Restriction Rules. WITH SECURITY_ENFORCED only checks CRUD/FLS, not Restriction Rules. Queries in SYSTEM_MODE or without sharing classes using default SOQL bypass Restriction Rules entirely. This distinction matters for “Chinese Wall” or “Minimum Necessary” HIPAA scenarios.

Scoping Rules

Scoping Rules do not restrict access; they provide a default “scope” (filter) for the user’s view (e.g., “My Active Accounts”). Available on custom objects, Account, Case, Contact, Event, Lead, Opportunity, and Task.

  • Behavior: Applies a default filter to list views, reports, and lookups. Does not remove access; only changes the default view.
  • Apex: Scoping rules are enforced when Apex runs in user context via WITH USER_MODE. Use USING SCOPE EVERYTHING to bypass scoping rules and return all records the user can access.
  • Use Case: Focusing large datasets for specific user groups without changing underlying sharing or security.

Named Credentials

Named Credentials store callout endpoint URLs and authentication details, keeping secrets out of Apex code. They are central to secure integrations.

Why Named Credentials Matter

Without Named CredentialsWith Named Credentials
Credentials hardcoded or in Custom SettingsCredentials managed declaratively
Endpoint URLs in codeEndpoint URL configured in Setup
OAuth token management in ApexPlatform handles token lifecycle
Secrets in version controlSecrets stored securely by platform
Each developer manages authAuth configured once, used everywhere

Named Credential Usage in Apex

// Callout using Named Credential - no auth code needed
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:My_ERP_System/api/orders'); // Named Credential prefix
req.setMethod('GET');
Http http = new Http();
HttpResponse res = http.send(req);
// Platform automatically adds authentication headers

Named Credential vs Remote Site Setting

FeatureNamed CredentialRemote Site Setting
Stores auth detailsYesNo
Token managementAutomatic (OAuth)Manual (in code)
CSRF protectionBuilt-inManual
Per-user vs Named PrincipalBothN/A
Use caseAuthenticated calloutsUnauthenticated callouts only

CTA Architecture Rule

Always recommend Named Credentials for callouts. If a CTA scenario shows Apex code with hardcoded credentials or manual OAuth token management, flag it as a security risk and recommend Named Credentials as the fix.

Apex Crypto

The Crypto class provides cryptographic functions for signing, encrypting, hashing, and generating MACs.

Common Crypto Use Cases

OperationMethodUse Case
HashingCrypto.generateDigest('SHA-256', data)Data integrity verification
HMACCrypto.generateMac('HmacSHA256', data, key)Webhook signature validation
EncryptionCrypto.encrypt('AES256', key, iv, data)Encrypt sensitive data in transit
DecryptionCrypto.decrypt('AES256', key, iv, data)Decrypt received data
Digital SignatureCrypto.sign('RSA-SHA256', data, privateKey)Sign outbound messages
RandomCrypto.getRandomInteger() / Crypto.generateAesKey(256)Generate tokens, keys

Crypto Key Management

Never hardcode encryption keys in Apex. Use Custom Settings (Protected), Named Credentials, or Platform Encryption’s key management. Keys in code are visible to anyone with “Author Apex” permission and are stored in version control.

Secure Coding Practices

SOQL Injection Prevention

SOQL injection occurs when user input is concatenated directly into SOQL queries.

// VULNERABLE - user input directly in query string
String userInput = ApexPages.currentPage().getParameters().get('name');
String query = 'SELECT Id, Name FROM Account WHERE Name = \'' + userInput + '\'';
List<Account> accounts = Database.query(query); // SOQL INJECTION RISK
// SAFE - use bind variables
String userInput = ApexPages.currentPage().getParameters().get('name');
List<Account> accounts = [SELECT Id, Name FROM Account WHERE Name = :userInput];
// SAFE - use String.escapeSingleQuotes() for dynamic SOQL
String userInput = ApexPages.currentPage().getParameters().get('name');
String safe = String.escapeSingleQuotes(userInput);
String query = 'SELECT Id, Name FROM Account WHERE Name = \'' + safe + '\'';
List<Account> accounts = Database.query(query);

SOQL Injection

Always use bind variables for static SOQL. For dynamic SOQL, always use String.escapeSingleQuotes(). This is a top Salesforce security review finding and will be flagged in any architecture review.

Cross-Site Scripting (XSS) Prevention

XSS occurs when user-supplied data is rendered in HTML without encoding.

ContextPreventionVisualforceLWC
HTML bodyHTML encode{!HTMLENCODE(value)} or <apex:outputText escape="true"/>Automatic (template auto-escapes)
JavaScriptJavaScript encode{!JSENCODE(value)}Use properties, not innerHTML
URL parametersURL encode{!URLENCODE(value)}Use NavigationMixin
CSSCSS encodeAvoid dynamic CSS valuesAvoid dynamic CSS values

Other Secure Coding Practices

VulnerabilityPrevention
CSRFUse Salesforce anti-CSRF tokens (built into VF/LWC); validate state parameter in OAuth
Open redirectValidate redirect URLs against allowlist; never use user input directly
Insecure direct object referenceAlways check CRUD/FLS/sharing before returning records
Sensitive data exposureUse Shield Encryption; mask fields in UI; avoid logging sensitive data
Insufficient loggingLog security events; use Event Monitoring; implement Transaction Security

Session-Based Permission Sets

Session-based Permission Sets activate only when specific conditions are met during a user’s session. They provide conditional, elevated access.

How Session-Based Permission Sets Work

Permission set that activates mid-session based on a trigger event, granting elevated access only for the duration of the qualifying session rather than permanently.
Figure 4. Session-based permission sets grant elevated access only when a specific event triggers activation, such as step-up authentication or approval, and automatically expire when the session ends. This limits the window of elevated access compared to permanent permission set assignments.

Use Cases for Session-Based Permission Sets

ScenarioImplementation
Step-up authenticationUser re-authenticates to access sensitive data; session PS grants access to encrypted fields
Time-limited elevated accessApproval workflow activates session PS for data export during audit period
Location-based accessFlow checks IP range; activates session PS for internal network users
Role-based escalationManager approves temporary access; Flow activates session PS for the requesting user

Activating Session-Based Permission Sets

MethodHowBest For
FlowSessionPermissionSetActivation elementMost scenarios; declarative
Auth ProviderCustom Auth Provider registration handlerSSO-triggered activation
ApexSessionPermissionSetActivation record insertComplex logic or API-driven
Connected AppSession-based PS assigned to Connected AppIntegration-specific access

Secure Integration Callout Architecture

When designing integrations, the CTA board expects you to articulate the full security stack for callouts, not just “use Named Credentials.”

Full security stack for Apex callouts, layering sharing enforcement and FLS in code, Named and External Credentials for auth management, and TLS plus mTLS for wire security.
Figure 5. Secure callout architecture separates concerns across three layers: Apex enforces sharing and FLS before the request leaves, Named and External Credentials manage authentication without hardcoding secrets, and TLS with optional mutual TLS protects the wire.

Enhanced External Credentials Model

The modern architecture separates concerns: the Named Credential stores the endpoint URL, the External Credential stores the authentication configuration, and a Permission Set controls which users/integrations can use that credential. Multiple Named Credentials can share one External Credential, reducing duplication.

Security in LWC vs Visualforce vs Aura

Security AspectLWCAuraVisualforce
XSS protectionAuto-escaped templatesSemi-auto ({!v.value} is safe, $A.util.format is not)Manual (HTMLENCODE, JSENCODE)
CSRF protectionBuilt-inBuilt-inBuilt-in (anti-CSRF token)
Data accessVia Apex (enforce in Apex)Via Apex (enforce in Apex)Direct binding (enforce FLS in controller)
Namespace isolationShadow DOMLocker ServiceNone
CSP enforcementStrictModerateRelaxed

Sources

Personal study notes for the Salesforce CTA exam. Content compiled from VJ's study notes, official Salesforce documentation, community sources, and online publicly available content, then organized and presented with AI assistance. Not affiliated with Salesforce. © 2025–2026 VJ Srivastava.