Module 5 — ANSWER SHEET (SEALED)
Companion to 05_Topic05_Security_Sharing.md — open ONLY after you have written your own attempt.
Protocol (file 00, M1→M5): write ≥2 hypotheses + 2 solution attempts before reading. Then compare your attempt against this sheet like an investigator compares a suspect's story to the evidence. The contrast IS the learning. Then REDO from memory, then do the drill.
INCIDENT 1 — THE SEARCH THAT RETURNED EVERYTHING
The problem restated
"Private" org, no-keyword class, LWC search returns 40 accounts the rep can't access. Why, and what's the complete fix?
Model answer (2-min interview version)
- The context trap: Apex classes with no sharing keyword run in SYSTEM MODE — record access AND field access filters are off. OWD/Private is enforced by the UI and by Apex only when the class declares it. The dev conflated UI enforcement with Apex enforcement.
- The three keywords:
with sharing= enforce the running user's record access;without sharing= bypass (privileged jobs — justify + comment);inherited sharing(API 45+) = inherit the caller's mode — the recommended default (prevents accidental over- and under-sharing). - The complete fix (two layers): layer 1 = the keyword (
inherited sharing); layer 2 = FLS —with sharingdoes NOT filter fields:Security.stripInaccessible(AccessType.READ, results).getRecords()(API 50+) or... WITH USER_MODE(API 59+). Hygiene: return only what the UI renders, validate input, paginate. - One-liner: "Declare the context — never trust the org."
Self-grade — you "got it" if you named:
- No keyword = system mode (the exact trap)
- The three keywords + inherited as default
- Two-layer fix: keyword (records) + stripInaccessible/USER_MODE (fields)
- UI vs Apex enforcement conflation
THE REDO — model answer
public inherited sharing class AccountSearch {
@AuraEnabled
public static List<Account> find(String q) {
return Security.stripInaccessible(AccessType.READ,
[SELECT Id, Name FROM Account WHERE Name LIKE :q]).getRecords();
}
}
// Layer 1: inherited sharing (record access) · Layer 2: stripInaccessible (FLS)RETRIEVAL DRILL — model answers
- Default context? → System mode — no sharing, no FLS.
- Three keywords? →
with sharing(enforce user's record access),without sharing(bypass — privileged),inherited sharing(API 45+, inherit caller — recommended default). - Why doesn't
with sharingfully protect? → Record access only; fields are a separate FLS filter Apex bypasses by default. Security.stripInaccessible? → API 50+, strips denied fields (READ/UPDATE), returnsStripInaccessibleResult(records + removed fields).- USER_MODE vs SYSTEM_MODE? →
WITH USER_MODEenforces CRUD/FLS at query time (API 59+);WITH SYSTEM_MODE= explicit bypass (the default).
INCIDENT 2 — THE FIELD THE REP SHOULDN'T HAVE SEEN
The problem restated
Profile denies fields; the UI hides them; a custom component renders them anyway. Which filter is missing and what's the three-level enforcement?
Model answer (2-min interview version)
- The false assumption: FLS is enforced by the UI layer (page layouts + field security) — NOT by Apex. A class selecting restricted fields returns them; a custom component renders whatever it received.
with sharingis record-access only — fields are a separate filter. - The three-level enforcement hierarchy: (1) describe check —
Schema.SObjectType.Candidate__c.fields.SSN__c.isAccessible()(granular, verbose); (2)Security.stripInaccessible(AccessType.READ, records).getRecords()(API 50+, bulk-safe, strips denied fields); (3)WITH USER_MODE(API 59+, enforcement inside the query, no post-processing). - Defense-in-depth (4 points): minimal projection (never SELECT sensitive fields "just in case"); FLS enforcement as the belt; UI renders only what was sent; dedicated sensitive endpoints with explicit permission gates (Custom Permission +
FeatureManagement.checkPermission). - One-liner: "The UI hides; Apex must strip — and never SELECT what the UI shouldn't see."
Self-grade — you "got it" if you named:
- UI enforces FLS, Apex doesn't
- Three levels: describe / stripInaccessible (50+) / USER_MODE (59+)
-
StripInaccessibleResult(records + removed fields) - Minimal projection + dedicated sensitive endpoints
THE REDO — model answer
public with sharing class CandidateOverview {
@AuraEnabled
public static Candidate__c get(String id) {
return Security.stripInaccessible(AccessType.READ,
[SELECT Id, Name FROM Candidate__c WHERE Id = :id]).getRecords();
// no sensitive fields in the SELECT; FLS enforced; DTO-style minimal projection
}
}RETRIEVAL DRILL — model answers
- Who enforces FLS in UI, who doesn't in Apex? → UI (layouts + field security); Apex returns whatever the query selects.
- Three mechanisms? → Describe
isAccessible()(verbose);Security.stripInaccessible(API 50+, bulk);WITH USER_MODE(API 59+, query-time). StripInaccessibleResultgives? →getRecords()(stripped) +getRemovedFields().- USER_MODE vs SYSTEM_MODE? → USER_MODE enforces CRUD/FLS in-query; SYSTEM_MODE bypasses (default).
- Minimal-projection rule? → Never SELECT fields the UI shouldn't see; sensitive data via dedicated gated endpoints.
INCIDENT 3 — THE SEARCH BOX THAT RAN YOUR SOQL
The problem restated
Concatenated dynamic SOQL + payload → tautology. Reconstruct, defend, and answer the two follow-ups.
Model answer (2-min interview version)
- The reconstructed query:
... WHERE Name LIKE '%%' OR Id != '' OR Name LIKE '%%'— a tautology: every row matches. User input became query syntax. - The three defense layers: (1) bind variables —
WHERE Name LIKE :pattern(value bound, never parsed as syntax — kills the injection class); (2)String.escapeSingleQuotes()— for unavoidable dynamic strings (a patch, not a design); (3) whitelisting — dynamic object/field names can't be bound → validate againstSchemadescribe or a hardcoded allowlist. User input is never a name. - Follow-up 1:
escapeSingleQuotesis a patch on the concatenation pattern — it escapes a quote character, not the pattern; attackers target other surfaces (operators, numeric contexts). The fix is removing concatenation ("don't sanitize, restructure"). - Follow-up 2: binding carries values only — field names, object names,
ORDER BYcolumns need allowlists.
Self-grade — you "got it" if you named:
- The tautology reconstruction
- Bind variables as primary defense
- escapeSingleQuotes as patch
- Whitelisting for names (can't be bound)
THE REDO — model answer
String pattern = '%' + input + '%';
List<Account> recs = [SELECT Id, Name FROM Account WHERE Name LIKE :pattern];
// Dynamic names:
Set<String> allowed = new Set<String>{'Name','City','Zip'};
if (!allowed.contains(column)) { /* reject */ }
String q = 'SELECT Id, Name FROM Account ORDER BY ' + column; // allowlisted onlyRETRIEVAL DRILL — model answers
- Injected query? →
WHERE Name LIKE '%%' OR Id != '' OR Name LIKE '%%'— matches everything. - Three defenses? → Bind variables; escapeSingleQuotes (legacy); whitelist/describe-validate names.
- Why isn't escaping complete? → Patch on concatenation; the pattern (user input in a query string) remains the hole — restructure with binding.
- What can't be bound? → Names (fields, objects, ORDER BY) — allowlists replace them.
- One-line rule? → User input is never query syntax and never a name.
INCIDENT 4 — THE NIGHTLY JOB THAT LEAKED PRIVATE DATA
The problem restated
Un-keyworded batch writes aggregates to a Public Read object; reps see other teams' revenue. Trace the chain and design the corrected job.
Model answer (2-min interview version)
- The three-link chain: (1) no keyword → system mode reads all deals (intended for a summary job); (2) un-keyworded class = undocumented privilege (nobody decided); (3) the actual leak — the destination:
Team_Summary__cOWD = Public Read → the summary inherits the destination's OWD, not the source's. Reviewers audit the read path and miss the write path. - The sharing chain (memorize): OWD (baseline) → role hierarchy (managers inherit, when enabled) → sharing rules (declarative group grants) → manual/Apex sharing (per-record
__Sharegrants). Cumulative. - The corrected design: destination OWD Private + sharing rule (owner-based, "Team Managers" group); keyword policy —
without sharingonly with justification AND access-controlled output (elseinherited sharing); defense-in-depth — correct ownership on rows + read-back test (rep-context query returns zero rows). - One-liner: "System-mode reads are fine; system-mode writes into public objects are the breach — the leak is the destination."
Self-grade — you "got it" if you named:
- Three-link chain (keyword, aggregation, destination OWD)
- "Leak is the destination"
- The 4-link sharing chain
- Private + sharing rule + read-back test
THE REDO — model answer
Team_Summary__c: OWD Private + sharing rule (owner-based → Team Managers group)
Batch: without sharing ONLY with justification (needs all deals) — output access-controlled
Read-back test: rep-context [SELECT ... FROM Team_Summary__c] returns 0 rowsRETRIEVAL DRILL — model answers
- Un-keyworded batch context? → System mode.
- Where was the leak? → The destination (Public Read object) — reviewers audit the read path, miss the write path.
- Sharing chain? → OWD → hierarchy → sharing rules → manual/Apex sharing.
- When is
without sharinglegitimate? → Job genuinely needs all data AND output is access-controlled — with a justification comment. - Sharing rule vs manual share? → Rules: declarative, group-based, automatic. Manual: per-record user grants (
__Sharerows, Apex-managed).
INCIDENT 5 — THE HIERARCHY THAT OPENED EVERY DOOR
The problem restated
"Private" org; a director sees everything. Trace the access math and design genuine confidentiality.
Model answer (2-min interview version)
- The access math (four sources): (1) OWD (baseline — Private here); (2) role hierarchy — everyone above the owner in the role tree inherits (roles are a tree, teams are a fiction); (3) sharing rules — the "Confidential" criteria rule granted ALL confidential deals to ALL "Directors+"; (4) manual/Apex sharing (none here). The picklist is data, not access: criteria rules only grant, never deny, and the hierarchy never reads the flag — the "confidential" marker made the data MORE visible.
- Hierarchy mechanics: "Grant Access Using Hierarchies" checkbox per object; bypassed by
without sharing, View All Data, system context. - Confidential design: hierarchy OFF for confidential objects (or a dedicated Private object with hierarchy off); rules scoped to named groups, never "roles and above"; access by explicit grants — structure, not flags.
- One-liner: "Access is structure; flags are data."
Self-grade — you "got it" if you named:
- Four access sources
- Role tree vs team chart
- Criteria rules grant, never deny; hierarchy ignores flags
- Confidential = hierarchy off + named groups + explicit grants
THE REDO — model answer
Deal__c: Private + hierarchy DISABLED for confidential data
Sharing: grant to named "Confidential Reviewers" group (never "Directors+")
Confidential__c: display/process field only — never the access controlRETRIEVAL DRILL — model answers
- Four sources? → OWD, role hierarchy, sharing rules, manual/Apex sharing.
- Hierarchy control + bypasses? → "Grant Access Using Hierarchies" per object; bypassed by
without sharing, View All Data, system context. - Why does the criteria rule on the flag grant MORE? → Criteria rules add access; nothing reads the flag to deny; the hierarchy never reads it.
- Three-point confidential design? → Hierarchy off (or dedicated object), rules to named groups, explicit grants.
- "Roles are a tree, teams are a fiction"? → Role position (not team membership) decides hierarchy inheritance — mis-arranged roles include other teams "below."
INCIDENT 6 — THE PERMISSION SET THAT BECAME A CROWN
The problem restated
A sensitive custom permission in an "Everyone" group; 400 users hold it; audits fail. The four entities and the governance design.
Model answer (2-min interview version)
- The four entities: profiles = baseline licenses + baseline access (lean by design); permission sets = granular additions without profile changes (the modern practice); permission set groups = role-based bundles; custom permissions = code-checked capability flags —
FeatureManagement.checkPermission('Name'). - The "Everyone" failure: bundling a sensitive capability into a broad group inverts the model — the capability became org-wide by assignment; custom permissions are invisible in most UIs, so nobody sees the crown until the audit. The Apex check only gates code paths — it can't stop a user who already has the flag. The blast radius is the assignment.
- Governance design (4 points): (1) capability ≠ role — smallest justified group; (2) lean profiles + permission sets + groups; (3) audit: permission-set-assignment reports, group-membership reports, quarterly access reviews; (4) logging around sensitive checks — the trail shows who used the capability.
- One-liner: "A permission is a key, not a badge — smallest set, smallest people, reviewed quarterly."
Self-grade — you "got it" if you named:
- Four entities (one line each)
- Assignment = blast radius; check gates code paths only
- Lean profile + PS + groups practice
- Reports + quarterly reviews + logging
THE REDO — model answer
Custom permission Manage_Compensation__c:
removed from "Everyone" group → assigned to "HR Compensation" group (justified membership)
checks: FeatureManagement.checkPermission(...) + logging on sensitive actions
Audit: permission-set-assignment report + group membership review — quarterlyRETRIEVAL DRILL — model answers
- Four entities? → Profiles (baseline), permission sets (additions), groups (bundles), custom permissions (code-checked flags).
- Why is the Everyone group a crown? → Broad assignment inverts the model; invisible in UIs until audit; the code check can't stop holders.
- What does
FeatureManagement.checkPermissiongate? → Code paths (sensitive actions) — not assignment; logging proves usage. - Modern practice? → Lean profiles + permission sets + groups; smallest-group assignment.
- Two audit surfaces? → Permission-set-assignment reports + quarterly access reviews (group membership).
INCIDENT 7 — THE ENDPOINT THAT SERVED EVERYONE'S DATA
The problem restated
REST endpoint: full_access scope, without sharing, concatenated input, raw serialization. The five layers and the redesign.
Model answer (2-min interview version)
- The five layers: (1) auth & scopes — Connected App scopes define the token's power; least privilege, never
full_access; (2) CRUD/FLS + sharing — class context + enforcement (stripInaccessible/USER_MODE); (3) input validation — binding,String.isId, allowlists; (4) rate limiting — Connected App request limits (the 14,000-requests bot); (5) audit — Event Monitoring + login history. - The redesign: scope →
api(no full_access); class →with sharing; permission gate →Schema.SObjectType.Account.isAccessible()+ custom permission (FeatureManagement.checkPermission) + FLS; input → bind the Id +String.isId(); response → a DTO (5 fields) — neverJSON.serialize(recs)of raw SObjects (it serializes every field and every FLS gap). - One-liner: "Trust the scope, not the story — and the response is the last filter: DTO it."
Self-grade — you "got it" if you named:
- Five layers (scopes, CRUD/FLS+sharing, input, rate limit, audit)
- full_access anti-pattern
- Permission gate (CRUD + custom permission) + FLS
- DTO response — never raw serialization
THE REDO — model answer
@RestResource(urlMapping='/partner/accounts/*')
global with sharing class PartnerAccountAPI {
@HttpGet
global static PartnerDTO get() {
String id = RestContext.request.requestURI.substringAfterLast('/');
if (!Schema.SObjectType.Account.isAccessible()) throw ...;
if (!FeatureManagement.checkPermission('Partner_Read')) throw ...;
List<Account> recs = Security.stripInaccessible(AccessType.READ,
[SELECT Id, Name FROM Account WHERE Id = :id WITH USER_MODE]).getRecords();
return new PartnerDTO(recs); // 5 fields only
}
}RETRIEVAL DRILL — model answers
- Five layers? → Auth/scopes, CRUD/FLS+sharing, input validation, rate limiting, audit.
- Why is full_access the anti-pattern? → Org-wide API access for any token using the app — least-privilege scopes per integration.
- Permission gate — two checks in order? → Object CRUD (
isAccessible) → capability (custom permission); plus FLS on the result. - Why is raw serialization a leak? → It serializes every selected field (and FLS gaps) — a DTO with exactly the intended fields is the response filter.
- Input validation tools? → Bind variables +
String.isId/pattern + field allowlists.
INCIDENT 8 — THE LEAK NOBODY COULD TRACE
The problem restated
Three compliance questions, zero answerable. The surface map and the audit-minimum.
Model answer (2-min interview version)
- The surface map: Field History — who changed record fields (per-object policy); Field Audit Trail — the Setup-managed tracking policy; Setup Audit Trail (
SetupAuditTrail) — who changed Setup (evidence only if read); Login History — who authenticated; Event Monitoring — who accessed data (API, exports, UI) — the ONLY surface that proves reads; Shield FLE — at-rest encryption (defense-in-depth, not audit). - The answer key: Q1 (who changed the record) → Field History (was disabled — "costs storage" was a policy decision that made the question unanswerable); Q2 (who changed the permission) → Setup Audit Trail (existed but unread); Q3 (who SAW the data) → Event Monitoring (structurally unanswerable without it — reads leave no trace anywhere else).
- The audit-minimum (5 points): Field History on sensitive objects (tracked fields, budgeted); scheduled
SetupAuditTrailreview (monthly); Event Monitoring + anomaly alerting (bulk exports, off-hours); Shield FLE on crown jewels; retention + compliance mapping. - One-liner: "Field History proves changes; Event Monitoring alone proves reads."
Self-grade — you "got it" if you named:
- Six surfaces (one line each)
- Q1→Field History, Q2→SetupAuditTrail, Q3→Event Monitoring
- Event Monitoring = only read-proof
- Audit-minimum 5 points (incl. anomaly alerting + Shield)
THE REDO — model answer
Enable: Field History (sensitive objects, tracked fields)
Schedule: SetupAuditTrail SOQL review — monthly
License: Event Monitoring (API + exports + UI) + anomaly alerts (bulk/off-hours)
Encrypt: Shield FLE on SSN/salary
Retain: per compliance; alert on the alertersRETRIEVAL DRILL — model answers
- Six surfaces? → Field History, Field Audit Trail, Setup Audit Trail, Login History, Event Monitoring, Shield FLE.
- Q1/Q2/Q3 mapping? → Q1 = Field History; Q2 = Setup Audit Trail; Q3 = Event Monitoring.
- Why only Event Monitoring proves reads? → Reads leave no trace in change logs or setup logs — only usage telemetry records them.
- Why isn't Shield audit? → At-rest encryption protects data; it records nothing about who saw what.
- Audit-minimum 5 points? → Field History policy, scheduled SetupAuditTrail review, Event Monitoring + anomaly alerts, Shield FLE, retention/compliance.
🏆 CAPSTONE — THE ORG WHERE ONE USER SAW EVERYTHING (model report)
- Clue-by-clue:
- A → Incident 1: no keyword = system mode. Fix:
inherited sharing+ FLS (stripInaccessible/USER_MODE). - B → Incident 2: FLS enforced by UI, not Apex. Fix: minimal projection + FLS enforcement + component renders only what was sent.
- C → Incident 7: full_access scope + without sharing + concatenation + raw serialization + no rate limit. Fix: least-privilege scope,
with sharing, binding +String.isId, DTO, rate limits, Event Monitoring. - D → Incident 5: hierarchy enabled + over-broad criteria rule. Fix: hierarchy off for confidential + named-group rules + explicit grants.
- E → Incident 6: sensitive custom permission in "Everyone" group. Fix: smallest-group assignment + quarterly reviews + assignment reports + logging.
- F → Incident 4: batch wrote to Public Read destination. Fix: destination Private + sharing rule + keyword policy + read-back test.
- G → Incident 8: no Field History, unread Setup Audit Trail, no Event Monitoring. Fix: Field History policy, scheduled review, Event Monitoring + alerts, Shield.
- A → Incident 1: no keyword = system mode. Fix:
- Priorities: Tonight — stop the bleeding: revoke partner tokens + disable endpoint (C), restrict "Everyone" (E), deactivate leaky classes (A, B). This week — fix classes (keywords + FLS + DTO), destination OWD + rules (F), confidential-data hierarchy/rules (D), enable Field History + Event Monitoring (G). This quarter — the security program: five-layer endpoint standard, access-review cadence, audit-minimum, security regression in every release.
- The shared root cause: "The org assumed the platform filters by default: no keywords, no FLS enforcement, no scopes, no destination security, no access reviews, no audit — security was 'the platform's job,' so nobody declared anything." One disease: security by assumption, not by declaration.
- The 3 verification steps: (a) context regression — sandbox user cloned from a rep profile runs every new Apex entry point; results must match the profile's record/field access exactly; (b) endpoint penetration smoke — limited-scope token + injection payloads: denied fields absent, injection returns no rows, rate limit trips; (c) audit proof — rep-context query on the summary returns zero rows; Field History/Event Monitoring logs exist and are queryable.
- The 2-minute answer (say out loud): "The org's security failure is one disease: security by assumption. We assumed the platform filters by default — so classes ran system-mode, endpoints trusted scopes, destinations inherited public OWD, permissions were assigned to everyone, and nobody enabled audit. The fix is a program: every class declares its context, every endpoint passes the five-layer standard, every sensitive destination is access-controlled, access is reviewed quarterly, and Event Monitoring proves what happened — so the next question is answerable the same day, not in a breach investigation. Tonight I stop the bleeding — the endpoint, the Everyone group, and the two leaky classes — and from Monday, security is declared, never assumed."
KNOWLEDGE SPINE — rapid-fire (model answers)
- Default context of a no-keyword class? → System mode (no sharing, no FLS).
- Three sharing keywords? → with / without / inherited (API 45+).
- Recommended default keyword? →
inherited sharing. - Does
with sharingenforce FLS? → No — record access only. - FLS bulk one-liner? →
Security.stripInaccessible(AccessType.READ, records)(API 50+). - Query-time FLS one-liner? →
WITH USER_MODE(API 59+). - OWD values? → Private / Public Read Only / Public Read-Write / Controlled by Parent.
- Sharing chain? → OWD → hierarchy → rules → manual/Apex.
- Hierarchy bypasses? →
without sharing, View All Data, system context. - Criteria-based sharing rules can only? → Grant (never deny).
- SOQL injection defenses? → Bind variables, escapeSingleQuotes, whitelist names.
- Can you bind a field name? → No — allowlists.
- Lean access practice? → Lean profiles + permission sets + groups.
- Custom permission check in Apex? →
FeatureManagement.checkPermission(). - Endpoint scopes? → Least privilege; never
full_access. - Never serialize what? → Raw SObjects — use a DTO.
- Proves record changes? → Field History.
- Proves Setup changes? → Setup Audit Trail.
- ONLY proof of reads? → Event Monitoring.
- At-rest protection? → Shield FLE (not audit).
INTERLEAVED PRACTICE SET — model answers
- Limit hunt: (a) Module 5 — system mode / no keyword (Incident 1); (b) Module 5 — FLS gap:
with sharingdoesn't filter fields (Incident 2); (c) Module 5 — SOQL injection (Incident 3); (d) Module 5 — destination leak: batch → Public Read object (Incident 4); (e) Module 5 —without sharingfrom a trigger: runs system-mode inside a user transaction — the keyword must be justified (Incident 1's contrast) — and Module 1's trigger-order context applies. - Design (2 min): Endpoint (Connected App,
apiscope, least privilege); five layers: auth/scopes, CRUD/FLS + sharing (with sharing+stripInaccessible), input validation (String.isId+ binding), rate limiting, audit (Event Monitoring); DTO with the 5 fields; prove access via Event Monitoring API events. - Module-1 bridge: two parts — (1) the class keyword:
without sharingwith justification (job needs all data) orinherited sharingwith privileged caller; (2) the destination: Private OWD + sharing rule + read-back test (Module 1's batch discipline × Incident 4's destination lesson). The keyword that silently under-reports:with sharingon a job that needs all data. - Module-4 bridge: flows run in system context by default — a flow writing sensitive aggregates is the declarative twin of the un-keyworded batch; the fix is the same: Private destination + sharing rules + fault path + reconciliation (Module 4 Incident 6), plus the access-matrix discipline (Module 4 Incident 5) for screen flows touching sensitive fields.
- One-card answer (5 bullets + incident map): (1) Cause: system mode — no keyword or
without sharing(I1); (2) Fix layer 1:with sharing/inherited sharingfor record access (I1); (3) Fix layer 2: FLS viastripInaccessible/WITH USER_MODE(I2); (4) Hygiene: minimal projection, never expose fields the UI doesn't render (I2); (5) Verify: Debug-as-user / cloned-profile user regression (I1/I7).
THE ONE-CARD ANSWER KEY (carry this)
"A user can't see data returned by your Apex class — why?" — 5 lines:
- Cause: the class runs in system mode (no sharing keyword, or
without sharing) — no record-level or field-level enforcement. - Fix layer 1 (records):
with sharing/inherited sharing— the keyword turns the running user's sharing back on. - Fix layer 2 (fields):
Security.stripInaccessible(AccessType.READ, records)(API 50+) orWITH USER_MODE(API 59+) —with sharingdoes NOT filter fields. - Design: minimal projection — never SELECT what the UI shouldn't see; dedicated gated endpoints for sensitive fields.
- Verify: test with a cloned-profile user (the rep's exact context), assert the returned record/field set matches the profile exactly.
Numbers/names to say cold: system mode default · with/without/inherited (45+) · stripInaccessible (50+) · USER_MODE (59+) · OWD: Private/PRO/P RW/Controlled by Parent · chain: OWD→hierarchy→rules→manual · bind variables / escapeSingleQuotes / whitelist · FeatureManagement.checkPermission · five endpoint layers · DTO responses · Field History / SetupAuditTrail / Event Monitoring (only read-proof) / Shield FLE.