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
| Keyword | Record Access | Use Case |
|---|---|---|
with sharing | Enforces the running user’s sharing rules (OWD, role hierarchy, sharing rules) | Default for most classes; user-facing logic |
without sharing | Ignores all sharing rules; sees all records | System operations, integrations, admin utilities |
inherited sharing | Inherits the sharing context of the calling class | Utility 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 transaction | Avoid; explicit is always better |
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
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
| Method | Enforces | Available Since | Where It Works |
|---|---|---|---|
WITH SECURITY_ENFORCED | Object CRUD + FLS (Read only) | Spring ‘20 (GA) | SOQL queries |
WITH USER_MODE | CRUD + FLS + Sharing | Spring ‘23 | SOQL + DML |
stripInaccessible() | FLS | Spring ‘20 (GA) | Apex (before DML or after query) |
Schema.describe | Manual check | Always | Apex (programmatic checks) |
Security.stripInaccessible() | FLS per operation type | Spring ‘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 fieldList<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 sharingfor that)
WITH USER_MODE / WITH SYSTEM_MODE
The modern approach. Available on both SOQL and DML.
// USER_MODE: Enforces CRUD + FLS + sharingList<Account> accounts = [ SELECT Name, Phone, AnnualRevenue FROM Account WHERE Industry = 'Technology' WITH USER_MODE];
// USER_MODE on DML: Enforces CRUD + FLSDatabase.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_MODEin inline SOQL throwsSystem.QueryExceptionfor inaccessible fields (similar toWITH SECURITY_ENFORCED)Database.querywithAccessLevel.USER_MODEmay 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 resultsList<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 insertList<Account> newAccounts = new List<Account>();// ... populate accountsSObjectAccessDecision insertDecision = Security.stripInaccessible( AccessType.CREATABLE, newAccounts);insert insertDecision.getRecords();AccessType options: READABLE, CREATABLE, UPDATABLE, UPSERTABLE
Enforcement Selection Guide
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.
| Aspect | Sharing Rules | Restriction Rules |
|---|---|---|
| Effect | Grants access (additive) | Narrows access (subtractive) |
| Precedence | Evaluated first | Evaluated after sharing |
| Use Case | Open access to regional teams | Hide sensitive/VIP records from all but a few |
| Limit | 300 ownership-based + 300 criteria-based per object | 2 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. UseUSING SCOPE EVERYTHINGto 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 Credentials | With Named Credentials |
|---|---|
| Credentials hardcoded or in Custom Settings | Credentials managed declaratively |
| Endpoint URLs in code | Endpoint URL configured in Setup |
| OAuth token management in Apex | Platform handles token lifecycle |
| Secrets in version control | Secrets stored securely by platform |
| Each developer manages auth | Auth configured once, used everywhere |
Named Credential Usage in Apex
// Callout using Named Credential - no auth code neededHttpRequest req = new HttpRequest();req.setEndpoint('callout:My_ERP_System/api/orders'); // Named Credential prefixreq.setMethod('GET');Http http = new Http();HttpResponse res = http.send(req);// Platform automatically adds authentication headersNamed Credential vs Remote Site Setting
| Feature | Named Credential | Remote Site Setting |
|---|---|---|
| Stores auth details | Yes | No |
| Token management | Automatic (OAuth) | Manual (in code) |
| CSRF protection | Built-in | Manual |
| Per-user vs Named Principal | Both | N/A |
| Use case | Authenticated callouts | Unauthenticated 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
| Operation | Method | Use Case |
|---|---|---|
| Hashing | Crypto.generateDigest('SHA-256', data) | Data integrity verification |
| HMAC | Crypto.generateMac('HmacSHA256', data, key) | Webhook signature validation |
| Encryption | Crypto.encrypt('AES256', key, iv, data) | Encrypt sensitive data in transit |
| Decryption | Crypto.decrypt('AES256', key, iv, data) | Decrypt received data |
| Digital Signature | Crypto.sign('RSA-SHA256', data, privateKey) | Sign outbound messages |
| Random | Crypto.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 stringString 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 variablesString userInput = ApexPages.currentPage().getParameters().get('name');List<Account> accounts = [SELECT Id, Name FROM Account WHERE Name = :userInput];
// SAFE - use String.escapeSingleQuotes() for dynamic SOQLString 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.
| Context | Prevention | Visualforce | LWC |
|---|---|---|---|
| HTML body | HTML encode | {!HTMLENCODE(value)} or <apex:outputText escape="true"/> | Automatic (template auto-escapes) |
| JavaScript | JavaScript encode | {!JSENCODE(value)} | Use properties, not innerHTML |
| URL parameters | URL encode | {!URLENCODE(value)} | Use NavigationMixin |
| CSS | CSS encode | Avoid dynamic CSS values | Avoid dynamic CSS values |
Other Secure Coding Practices
| Vulnerability | Prevention |
|---|---|
| CSRF | Use Salesforce anti-CSRF tokens (built into VF/LWC); validate state parameter in OAuth |
| Open redirect | Validate redirect URLs against allowlist; never use user input directly |
| Insecure direct object reference | Always check CRUD/FLS/sharing before returning records |
| Sensitive data exposure | Use Shield Encryption; mask fields in UI; avoid logging sensitive data |
| Insufficient logging | Log 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
Use Cases for Session-Based Permission Sets
| Scenario | Implementation |
|---|---|
| Step-up authentication | User re-authenticates to access sensitive data; session PS grants access to encrypted fields |
| Time-limited elevated access | Approval workflow activates session PS for data export during audit period |
| Location-based access | Flow checks IP range; activates session PS for internal network users |
| Role-based escalation | Manager approves temporary access; Flow activates session PS for the requesting user |
Activating Session-Based Permission Sets
| Method | How | Best For |
|---|---|---|
| Flow | SessionPermissionSetActivation element | Most scenarios; declarative |
| Auth Provider | Custom Auth Provider registration handler | SSO-triggered activation |
| Apex | SessionPermissionSetActivation record insert | Complex logic or API-driven |
| Connected App | Session-based PS assigned to Connected App | Integration-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.”
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 Aspect | LWC | Aura | Visualforce |
|---|---|---|---|
| XSS protection | Auto-escaped templates | Semi-auto ({!v.value} is safe, $A.util.format is not) | Manual (HTMLENCODE, JSENCODE) |
| CSRF protection | Built-in | Built-in | Built-in (anti-CSRF token) |
| Data access | Via Apex (enforce in Apex) | Via Apex (enforce in Apex) | Direct binding (enforce FLS in controller) |
| Namespace isolation | Shadow DOM | Locker Service | None |
| CSP enforcement | Strict | Moderate | Relaxed |
Related Topics
- Sharing Model:
with sharingenforces the sharing model in code - Field & Object Security: CRUD/FLS that Apex must enforce
- Shield Encryption: encrypted field access in Apex
- Identity & SSO: Named Credentials and OAuth in integrations
- Security Best Practices: secure coding patterns and anti-patterns
- Integration: Named Credentials and callout security
Sources
- Salesforce Help: Apex Security
- Salesforce Help: stripInaccessible
- Salesforce Help: SOQL Security Modes
- Salesforce Help: Named Credentials
- Salesforce Help: Session-Based Permission Sets
- Salesforce Developers: Secure Coding Guidelines
- OWASP: SOQL Injection Prevention
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.