sf-security-constraints

Enforce CRUD/FLS, sharing model, SOQL injection prevention, and XSS protection for Apex and LWC. Use when writing or reviewing ANY Apex, trigger, LWC, or VF page. Do NOT use for Flow-only configuration.

Security Constraints for Salesforce Code

When to Use

This skill auto-activates when writing, reviewing, or modifying any Apex class, trigger, LWC component, or Visualforce page. It enforces CRUD/FLS checks, sharing model compliance, SOQL injection prevention, and XSS protection for all Salesforce code.

Hard rules that apply to every Apex class, trigger, LWC component, and Visualforce page. Violating any constraint below is a blocking issue that must be fixed before the code is considered complete.

Reference: @../_reference/SECURITY_PATTERNS.md, @../_reference/SHARING_MODEL.md @../_reference/DEPRECATIONS.md


Never Do

#ConstraintWhy
N1Skip CRUD/FLS checks on user-facing SOQL or DMLExposes data the running user should not see or modify
N2Use without sharing without a written justification commentSilently bypasses record-level security for every query in the class
N3Build SOQL with string concatenation of user inputSOQL injection -- attacker can read or modify arbitrary records
N4Trust client-side input without server-side validationClient payloads are trivially forgeable; all validation must repeat in Apex
N5Use element.innerHTML = userInput in LWCDirect XSS vector; LWC auto-encoding is bypassed
N6Hardcode API keys, tokens, passwords, or record IDs in ApexCredentials leak via source control; IDs differ across orgs
N7Log sensitive field values with System.debugDebug logs are accessible to admins and can be exported
N8Omit the sharing keyword on a class entirelyDefaults to without sharing in most contexts -- an implicit security bypass

Always Do

#ConstraintHow
A1Enforce CRUD + FLS on user-facing queriesWITH USER_MODE in SOQL (see @../_reference/API_VERSIONS.md for minimum version)
A2Enforce CRUD + FLS on user-facing DMLDatabase.insert(records, false, AccessLevel.USER_MODE)
A3Use with sharing as the default class keywordSwitch to inherited sharing only for utility/helper classes
A4Use bind variables for SOQL filter valuesStatic SOQL :bindVar or Database.queryWithBinds()
A5Sanitize output in VisualforceHTMLENCODE, JSENCODE, JSINHTMLENCODE, URLENCODE per context
A6Use textContent or <lightning-formatted-rich-text> in LWCNever assign user-controlled strings to innerHTML
A7Store credentials in Named Credentials / External CredentialsUse callout:NamedCredential prefix in Apex HTTP requests
A8Document every without sharing usageInclude: why sharing bypass is needed, who approved, date reviewed
A9Whitelist-validate dynamic SOQL componentsSort fields, directions, object/field names must match a known-safe set
A10Use Security.stripInaccessible() when silent field removal is acceptableAlways check getRemovedFields() to avoid downstream NullPointerException

Sharing Keyword Decision

Apply the correct keyword on every class. Reference: @../_reference/SHARING_MODEL.md

User-facing code (LWC, VF, Aura, REST API)?     -->  with sharing
Utility / helper called from mixed contexts?     -->  inherited sharing
Scheduled batch / system-only processing?        -->  without sharing  (document justification)
Trigger handler?                                 -->  with sharing  (call without sharing helper only if justified)
Inner class?                                     -->  declare explicitly  (does NOT inherit outer class keyword)
Omitted / unsure?                                -->  with sharing

Key rule: sharing context does not propagate to called classes. A with sharing class calling a without sharing class runs the called method without sharing.


Anti-Pattern Table

Anti-PatternSecurity ImpactCorrect Pattern
Database.query('... WHERE Name = \'' + input + '\'')SOQL injection -- attacker reads/modifies arbitrary data[SELECT ... WHERE Name = :input WITH USER_MODE] or Database.queryWithBinds()
public class FooController { ... } (no sharing keyword)Implicit without sharing -- returns all recordspublic with sharing class FooController { ... }
public without sharing class AccountCtrl (user-facing)All records visible regardless of OWD, role hierarchy, sharing rulespublic with sharing class AccountCtrl
insert records; in user-facing codeNo CRUD/FLS enforcementDatabase.insert(records, false, AccessLevel.USER_MODE);
element.innerHTML = serverData in LWCStored/reflected XSSelement.textContent = serverData
req.setHeader('Authorization', 'Bearer ' + hardcodedToken)Credential in source code; leaks via SCMcallout:Named_Credential with External Credentials
{!rawMergeField} in VisualforceReflected XSS{!HTMLENCODE(rawMergeField)} or <apex:outputText escape="true">
System.debug('SSN: ' + contact.SSN__c)PII in debug logsRemove sensitive field logging; use opaque identifiers
API_Keys__c.getOrgDefaults().Token__c for authSecrets in queryable Custom SettingExternal Credentials (see @../_reference/API_VERSIONS.md for minimum version)
SOQL without LIMIT in user-facing contextUnbounded query; governor limit risk + data exposureAdd LIMIT clause; paginate with OFFSET or cursor

CRUD/FLS Enforcement Quick Reference

Reference: @../_reference/SECURITY_PATTERNS.md

ApproachMin APIEnforcesOn Violation
WITH USER_MODE (SOQL)See @../_reference/API_VERSIONS.mdCRUD + FLSThrows QueryException
AccessLevel.USER_MODE (DML)See @../_reference/API_VERSIONS.mdCRUD + FLSThrows DmlException
WITH SECURITY_ENFORCED (SOQL)See @../_reference/API_VERSIONS.mdCRUD + FLS (reads)Throws QueryException
Security.stripInaccessible()See @../_reference/API_VERSIONS.mdFLS onlySilently strips fields
Manual isAccessible() / isCreateable()AllCRUD onlyDeveloper-controlled

Prefer WITH USER_MODE / AccessLevel.USER_MODE for all new code (see @../_reference/API_VERSIONS.md for minimum version). Fall back to stripInaccessible only when silent field removal is the desired behavior.


Visualforce Encoding Quick Reference

Output ContextEncoding FunctionExample
HTML bodyHTMLENCODE{!HTMLENCODE(account.Description)}
HTML attributeHTMLENCODEtitle="{!HTMLENCODE(account.Name)}"
JavaScript stringJSENCODEvar x = '{!JSENCODE(account.Name)}'
JS in HTML attributeJSINHTMLENCODEonclick="fn('{!JSINHTMLENCODE(val)}')"
URL parameterURLENCODEhref="/page?q={!URLENCODE(val)}"

Use <apex:outputField> or <apex:outputText escape="true"> where possible -- they handle encoding automatically.


Enforcement Priority

When reviewing or writing code, check constraints in this order:

  1. Sharing keyword declared on every class (N2, N8, A3)
  2. CRUD/FLS enforced on all user-facing SOQL and DML (N1, A1, A2)
  3. SOQL injection -- no string concatenation with user input (N3, A4, A9)
  4. XSS -- correct encoding in VF; no innerHTML in LWC (N5, A5, A6)
  5. Credentials -- no hardcoded secrets; use Named/External Credentials (N6, A7)
  6. Logging -- no sensitive data in debug statements (N7)