Join our Newsletter — 33% off our NHI Course
Home› Guides› Salesforce Apex and Lightning Secure Coding Guide: Sharing,…
Guide Application Security

Salesforce Apex and Lightning Secure Coding Guide: Sharing, CRUD and FLS

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 27 October 2026 13 min read
On this page

Custom Apex and Lightning code can quietly bypass the record sharing, object permissions and field-level security that Salesforce administrators spend years configuring. On most platforms, authorisation is something the developer adds; in Salesforce it already exists in the platform, and the most common secure coding failure is code that runs with more access than the user who triggered it. This guide explains how the Apex sharing keywords, user and system mode, Security.stripInaccessible and Lightning Web Security work, how to avoid SOQL injection and cross-site scripting, how to handle callout credentials, what to log, and the static analysis rules that catch common mistakes. It is for Salesforce developers, technical architects and the security teams that review custom code and AppExchange packages. For tenant-level configuration, see the Salesforce Security Guide.

Key takeaways

  • Sharing and CRUD/FLS are separate controls. The with sharing keyword controls which records a user can see; it does not enforce object or field permissions. You need both.
  • Declare everything explicitly. Salesforce recommends declaring a sharing mode on every class that performs SOQL or DML, and setting an explicit access mode on database operations, especially because defaults changed in API version 67.0.
  • Use user mode for user-driven data access. WITH USER_MODE, insert as user and AccessLevel.USER_MODE apply the running user's sharing, object permissions and field-level security in one step.
  • Bind, do not concatenate. Static SOQL with bind variables prevents SOQL injection; String.escapeSingleQuotes is the fallback for unavoidable dynamic queries.
  • Callout credentials belong in named and external credentials, not in code or custom settings, and every connected app and integration user is a non-human identity to inventory and scope.

How identity and access work in Apex

Every Apex transaction runs as a user, but how much of that user's access the code respects depends on three separate settings. Getting secure code right means reasoning about each one.

ControlWhat it governsHow code sets it
Record sharingWhich records (rows) the user may see or edit, from org-wide defaults, role hierarchy, sharing rules and manual sharesClass keyword: with sharing, without sharing, inherited sharing; user mode always applies sharing
Object permissions (CRUD)Whether the user may create, read, edit or delete a type of objectAccess mode on queries and DML (WITH USER_MODE, as user, AccessLevel.USER_MODE); Security.stripInaccessible
Field-level security (FLS)Which fields the user may read or editSame access modes; Security.stripInaccessible for graceful removal of fields

The sharing keywords

  • with sharing: enforces the current user's sharing rules. Salesforce recommends setting it explicitly so code runs in the current user's context.
  • without sharing: ignores the current user's sharing rules, so the class can reach records the user could not otherwise see. Salesforce recommends it only for classes that genuinely need system-level access, and mainly for targeted elevation.
  • inherited sharing: takes the caller's mode at run time. When the class is itself an entry point, such as an Aura controller, an @AuraEnabled method called from a Lightning web component, a Visualforce controller, an Apex REST service or asynchronous Apex, it runs with sharing. It runs without sharing only when called from an existing without sharing context.
  • No keyword: the Apex Developer Guide states that classes with no sharing keyword run with sharing from API version 67.0, while in 66.0 and earlier the mode depends on inheritance and entry point and otherwise defaults to without sharing. A codebase usually mixes API versions, so do not rely on the default.

User mode and system mode

Apex lets each database operation declare its access mode. For SOQL and SOSL, add WITH USER_MODE or WITH SYSTEM_MODE. For DML, write insert as user acc; or as system. For Database and Search methods, pass AccessLevel.USER_MODE or AccessLevel.SYSTEM_MODE; the *WithBinds query methods require this parameter. User mode applies the running user's sharing rules, field-level security and object permissions; in system mode, the class sharing keyword decides whether record sharing applies. Salesforce documents that from API version 67.0 Apex runs in user mode by default, while earlier versions default to system mode, which is a strong reason to state the mode on every operation.

  • Error handling in user mode: a user mode SOQL query reports all field-level errors at once through QueryException.getInaccessibleFields(); DML exposes field errors through DMLException.getDmlFieldNames(), and Database DML through SaveResult.getErrors(). Missing object permission raises a security exception.
  • Graceful degradation: Security.stripInaccessible takes records and an AccessType (READABLE, CREATABLE, UPDATABLE, UPSERTABLE) and returns copies with fields the user cannot access removed, including relationship fields. getRemovedFields() shows what was removed. Use it to filter query results before returning them to a component, and to clean records deserialised from untrusted JSON before DML. It enforces field and object permissions, not record sharing, and it throws on AggregateResult.
  • Elevation for one operation: Salesforce documents AccessLevel.withPermissionSetId() as a Developer Preview way to add a permission set's access to a single operation without carrying it over. Treat preview features as subject to change.

Admin roles and privileged code paths

System mode and without sharing are the Apex equivalents of privileged access. Treat every use as a privileged code path that needs a reason and a reviewer.

  • Isolate elevation: put the few operations that must run in system mode in small, separately named classes (for example ...ElevatedService) declared without sharing, called from with sharing entry points that perform their own authorisation check first.
  • Check the caller's right to the action, not just the data: before an elevated operation, check a custom permission or permission set assignment that represents the business action (for example "approve refunds"), not a profile name.
  • Triggers run in system context: Salesforce advises setting an explicit access mode on database operations in triggers and handler classes. Do not let trigger handlers become a path to update fields the user could not edit through the UI.
  • Guard @AuraEnabled and REST methods: any @AuraEnabled, @RestResource or webservice method is callable directly by an authenticated user with the right access to the class, not only through your component. Validate every parameter and enforce sharing and FLS inside the method.
  • Restrict Apex class access: grant access to controller classes through permission sets for the users who need the feature, rather than to all profiles.
  • Guest and community users: code reachable by unauthenticated site guest users needs the strictest review, because a single without sharing controller can expose records to the internet.

Injection and client-side security

SOQL and SOSL injection

The Apex Developer Guide describes SOQL injection as user input that modifies a dynamic SOQL statement, typically when a value is concatenated into a string passed to Database.query. Its guidance is to avoid dynamic SOQL and use static queries with bind variables (WHERE Name = :queryName), and, where dynamic SOQL is unavoidable, to sanitise user input with String.escapeSingleQuotes, which escapes single quotation marks. Field and object names cannot be bound, so check them against an allowlist built from the schema (for example from Schema.getGlobalDescribe()) rather than escaping them.

Cross-site scripting and redirects

  • Visualforce: rely on default output escaping and avoid disabling it; values from URL parameters need encoding for the context they are written into.
  • Apex error messages: addError with escaping disabled displays the message directly in the UI. PMD's ApexXSSFromEscapeFalse rule flags this.
  • Open redirects: do not build PageReference redirects from user-controlled values; PMD's ApexOpenRedirect rule flags redirects to user-controlled locations.
  • Lightning components: Salesforce describes Lightning Web Security (LWS) as a security architecture that runs components in an isolated JavaScript environment dedicated to their namespace. It replaced Lightning Locker as the default, is enabled by default for orgs created in Winter '23 and later, and became generally available for Lightning web and Aura components in all orgs as of Summer '23. LWS reduces the blast radius between namespaces; it does not make it safe to inject untrusted HTML into your own component, so avoid writing user data into the DOM as HTML.
  • Third-party libraries: load JavaScript as static resources reviewed like any dependency, and keep Content Security Policy trusted sites minimal.

Integrations, API tokens and service accounts

Most serious Salesforce data losses in recent years came through integrations rather than custom code bugs. In the ShinyHunters Salesforce data theft campaign, callers persuaded employees to authorise an attacker-controlled connected app, often a modified Data Loader, and then exported data through the API. In the Salesloft Drift breach, stolen OAuth tokens from one integration opened many Salesforce tenants. Code and configuration that handle integrations deserve the same rigour as authentication code.

PatternRiskSecure approach
Credentials in Apex, custom settings or custom metadataReadable by anyone with code or setup access; copied into sandboxesNamed credential for the endpoint plus external credential for authentication; PMD ApexSuggestUsingNamedCred flags hard-coded credentials in requests
Callouts over plain HTTPInterception and tamperingHTTPS only; PMD ApexInsecureEndpoint flags plain http endpoints
Hard-coded keys and IVs in Crypto callsEncrypted data is effectively unprotectedGenerated keys and IVs; PMD ApexBadCrypto flags hard-wired values
Integration user with System Administrator profileAny token theft becomes full tenant compromiseDedicated integration user with a minimal permission set, API-only where possible, IP restrictions where the platform allows
Any user can authorise new connected appsConsent phishing installs attacker appsRestrict who can approve and manage connected apps; allowlist approved apps; review scopes and owners

Salesforce describes a named credential as specifying a callout endpoint URL and its authentication parameters in one definition, with the authentication held in a linked external credential. External credentials support protocols including OAuth (browser, client credentials and JWT bearer flows), JWT, AWS Signature v4, basic and custom authentication, and access is granted through permission sets or profiles. Legacy named credentials are no longer updated or enhanced, so new code should use the Winter '23 model. Inventory every connected app, integration user, named credential and external credential with an owner and scopes. See the Securing Salesforce Agentforce Guide for agent actions, which also execute Apex and flows on a user's behalf.

Logging and detection

  • Event Monitoring log objects: Salesforce's object reference lists event log objects including ApexCalloutEventLog, ApexExecutionEventLog, ApexRestApiEventLog, ApexSoapApiEventLog, ApexTriggerEventLog, ApexUnexpectedExcpEventLog, AuraRequestEventLog, BulkApiEventLog and BulkApi2EventLog. Availability depends on licence. See the Salesforce Event Monitoring Detection Guide.
  • What to alert on: unusually large or new bulk exports, new connected app authorisations, API activity from unexpected networks, spikes in unexpected Apex exceptions (often a sign of probing @AuraEnabled methods), and callouts to new endpoints.
  • Application logging: log security decisions in elevated code paths (who requested, which permission was checked, which records were affected) without logging field values the user could not see.
  • Do not log secrets: never write tokens, session IDs or credential values to debug logs or custom log objects; debug logs are readable by administrators and support users.

Hardening baseline for custom code

  1. Explicit sharing on every class that runs SOQL, SOSL or DML, with with sharing as the default choice and inherited sharing for reusable services.
  2. Explicit access mode on every database operation: user mode for user-initiated work; system mode only in reviewed elevated classes.
  3. FLS on output: filter records with Security.stripInaccessible or user mode queries before returning them from @AuraEnabled or REST methods.
  4. Static SOQL with binds; allowlist any dynamic object or field names.
  5. No credentials in code or metadata: named and external credentials only.
  6. Static analysis in CI: run Salesforce Code Analyzer with the PMD Apex security rules (ApexCRUDViolation, ApexSharingViolations, ApexSOQLInjection, ApexXSSFromURLParam, ApexXSSFromEscapeFalse, ApexOpenRedirect, ApexInsecureEndpoint, ApexBadCrypto, ApexSuggestUsingNamedCred, ApexDangerousMethods) and triage findings. Note that the PMD ApexCRUDViolation description still assumes Apex runs in system mode by default, so check its findings against your API versions. See the SAST Triage and False Positive Management Guide.
  7. Security tests: write Apex tests that run as a restricted user with System.runAs and assert that inaccessible records and fields are not returned and that writes fail.
  8. Peer review gates: any new without sharing, SYSTEM_MODE, guest-accessible controller or connected app needs security review before deployment.

Rolling this out on an existing org

  1. Run the PMD security rules across the codebase to get a baseline count by rule.
  2. Fix guest-accessible and @AuraEnabled entry points first, then REST services, then batch and scheduled jobs.
  3. Add explicit sharing keywords and access modes class by class, with restricted-user tests, deploying to a full sandbox first.
  4. Gate new code in CI on the security rules, leaving legacy findings in a tracked backlog.
  5. If a change breaks a business process because users genuinely lacked access, fix the permission model rather than reverting to system mode; revert only the specific class while the permission change is agreed.

Framework mapping

FrameworkReferenceRelevance to Apex secure coding
OWASP ASVS 5.08.2.1, 8.2.2, 8.3.1 (authorisation); 1.2.4 (injection); 13.3.1 (secrets management)Function and data-level access, server-side enforcement, parameterised queries, vaulted secrets
NIST SP 800-53 Rev. 5AC-3 Access Enforcement; AC-6 Least PrivilegeSharing, CRUD and FLS enforcement; minimal elevated code paths and integration users
NIST SP 800-53 Rev. 5SA-11(1) Static Code Analysis; SI-10 Information Input ValidationPMD security rules in CI; SOQL binding and output encoding
NIST SP 800-53 Rev. 5IA-5 Authenticator Management; AU-2 Event LoggingNamed and external credentials; Event Monitoring coverage
NIST CSF 2.0PR.AA-05; PR.PS-06Least privilege access permissions; secure development integrated

Common misconfigurations

  • Assuming with sharing covers field security: it does not. A with sharing controller can still return salary or national ID fields the user cannot see on the page layout.
  • Classes with no sharing keyword: behaviour depends on API version and caller, which reviewers cannot reliably predict. PMD's ApexSharingViolations flags classes that use DML without an explicit sharing mode.
  • Trusting the component to restrict calls: an @AuraEnabled method that only your admin page calls is still callable by any user with access to the class.
  • Concatenated dynamic SOQL from list view filters, search boxes or sort parameters.
  • Integration users with broad profiles and long-lived tokens, and ordinary users able to authorise connected apps, as the integration breaches above show.
  • Secrets in custom settings or debug logs, copied into every sandbox refresh and visible to anyone with setup access.

Practitioner checklist

  • Declare with sharing, without sharing or inherited sharing on every class that queries or writes data.
  • Set an explicit user or system access mode on every SOQL, SOSL and DML operation.
  • Filter output with Security.stripInaccessible or user mode before returning records to components or APIs.
  • Validate parameters and enforce authorisation inside every @AuraEnabled, REST and webservice method.
  • Use static SOQL with bind variables and allowlist dynamic field and object names.
  • Move all callout credentials to named and external credentials and scope integration users with minimal permission sets.
  • Restrict connected app approval and inventory every connected app with owner and scopes.
  • Run the PMD Apex security rules in CI and triage the results.
  • Write restricted-user tests with System.runAs for every data-returning entry point.
  • Monitor Apex, API and bulk export event logs and alert on unusual exports and new app authorisations.

Standards and references

Related NHI Mgmt Group resources: Salesforce Security Guide · Securing Salesforce Agentforce Guide · Salesforce Event Monitoring Detection Guide · Secure Code Review Guide

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 27 October 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org