| name | apex-security-patterns |
| description | Use when designing, reviewing, or debugging Apex execution context, sharing keywords, CRUD/FLS enforcement, system-vs-user mode behavior, or secure write patterns. Triggers: 'with sharing', 'inherited sharing', 'stripInaccessible', 'AuraEnabled security', 'CRUD FLS'. NOT for SOQL injection review alone — use apex/soql-security for query-specific hardening. |
| category | apex |
| salesforce-version | Spring '25+ |
| well-architected-pillars | ["Security","Reliability"] |
| tags | ["apex-security","inherited-sharing","stripinaccessible","crud-fls","user-mode"] |
| triggers | ["with sharing without sharing inherited sharing decision","how do I enforce CRUD and FLS in Apex updates","AuraEnabled method running in system context","stripInaccessible for DML pattern","secure Apex service layer review","verify inner class and subclass sharing inheritance","check default sharing mode after API 67 version bump"] |
| inputs | ["entry point such as AuraEnabled, REST, Flow-invocable, trigger handler, or Batch","required data access model for records, objects, and fields","whether the code is read-heavy, write-heavy, or both"] |
| outputs | ["security design recommendation for execution context and access enforcement","review findings for sharing, CRUD/FLS, and system-context risks","secure service-layer pattern for reads and writes"] |
| dependencies | [] |
| version | 1.1.0 |
| author | Pranav Nagrecha |
| updated | "2026-07-07T00:00:00.000Z" |
Use this skill when Apex security needs to be explicit rather than assumed. The purpose is to choose the right sharing model, enforce CRUD and FLS deliberately on reads and writes, and prevent user-facing entry points from silently operating in broader system context than intended.
Before Starting
- What is the actual entry point:
@AuraEnabled, REST resource, invocable action, trigger helper, Queueable, or Batch?
- Should the code honor the caller’s record visibility, or is there a documented reason it must run with elevated access?
- Does the code only read data, mutate data, or dynamically choose fields or objects?
Core Concepts
Sharing Keywords Set The Record-Access Boundary
with sharing, without sharing, and inherited sharing are design choices, not style preferences. with sharing enforces row-level sharing rules for the class. without sharing explicitly widens record visibility and must be justified — for record-visibility purposes it lets the code see records as if the running user had Modify All Data. inherited sharing makes the class adopt the caller’s sharing model and is often the safest default for reusable service layers that should not surprise reviewers.
Do not lean on the default. In API version 67.0 and later, a class with no explicit sharing declaration runs in with sharing mode. That is a safer default than older versions, but it also means a class that genuinely needs elevated visibility can silently start enforcing sharing after an API-version bump, and older code being uplifted may change behavior. Declare the intended mode explicitly rather than depending on the version default.
Sharing Mode Resolves By Definition, Not By Call Site
Two inheritance and call-chain rules trip up reviewers who assume sharing "flows down" like a normal variable:
- A method's enforcement is fixed by where it is defined, not by who calls it. A method defined in a
with sharing class still enforces sharing rules even when called from a without sharing class, and vice versa. You cannot widen or narrow a method's sharing by changing the caller.
- Class inheritance and inner classes behave differently. A class without its own declaration that
extends a parent adopts the parent's sharing mode across the chain. But inner classes do not adopt the outer (container) class's mode — each inner class needs its own declaration or it falls back to the version default.
- resolves at the entry point. When an class is itself the top-level entry point — an Aura component controller, an method called from LWC, a Visualforce controller, an Apex REST service, or an asynchronous Apex class — it runs in . It runs only when explicitly called from an already-established context. This is what makes the least-surprising default for reusable services.