Salesforce Interview Prep

Module 5 — Security & Sharing Model

Interview weight: 10–15% (sharing model ~5%, CRUD/FLS + sharing keywords ~5%, security scenarios ~5% — and the research's warning: not mentioning CRUD/FLS/sharing at 3–4yr level is a visible gap) · Estimated time: 5–7 sessions (~90 min each) Target: By the end, you can explain the sharing model chain (OWD → role hierarchy → sharing rules → manual/Apex sharing), the three sharing keywords, the 2026-standard FLS enforcement tools (Security.stripInaccessible, WITH USER_MODE), profiles vs permission sets vs custom permissions, SOQL injection defense, REST endpoint security, and audit surfaces — and you never give a security answer without mentioning the fix. Security is the topic where juniors are silent and seniors are expected to volunteer the answer.


M0 — THE MAP (read this first, 5–10 min)

The one idea everything hangs on: SECURITY IS A LAYERED FILTER, AND APEX'S DEFAULT IS "NO FILTER"

Every concept in this module — sharing, CRUD/FLS, the three keywords, stripInaccessible, USER_MODE, profiles vs permission sets, injection, REST security, audit — is a consequence of one design fact:

Salesforce security is a stack of filters (object access → field access → record access → input trust → output audit). Apex, by default, runs in SYSTEM MODE: it bypasses the record-access filter (sharing) and the field-access filter (FLS) unless you explicitly turn the filters on. Everything "leaked" in this module leaked because someone assumed the platform filters by default — it doesn't, in Apex. Your job as the senior candidate is to name the filter stack and know exactly which filters Apex bypasses and which one-liners turn them back on.

Think of it as a secure building:

  • The building = the org. OWD = the front-door policy per floor (Private / Public Read Only / Public Read-Write / Controlled by Parent) — the baseline visibility.
  • The elevator = role hierarchy: managers ride above their team (when "Grant Access Using Hierarchies" is enabled); the hierarchy is the default exception to Private.
  • The doors = sharing rules (declarative, group-based extensions) and manual/Apex sharing (per-record grants) — the explicit exceptions.
  • The room keys = object/field access (CRUD/FLS): profiles set the base set of keys, permission sets add keys, custom permissions are the app-defined badges checked in code.
  • The guard at the Apex entrance = the sharing keyword: with sharing (enforce the filters), without sharing (bypass — privileged maintenance), inherited sharing (the modern default — inherit the caller's mode). The trap: no keyword = system mode = the guard is off.
  • The scanner = Security.stripInaccessible and WITH USER_MODE: the 2026 one-liners that re-enable the field/object filters on data after the query.
  • The parole office = audit: Field History (who changed what on a record), Audit Trail (who changed what in Setup), Event Monitoring (who saw what — the only surface that proves reads), Shield (encryption).
  • The X-ray = input validation: SOQL injection defense (bind variables, escapeSingleQuotes, whitelisting) — trust nothing from the UI.

Why this map matters (the bridge): The 2026 interviewer reality (agent 05 research): at 3–4yr, not mentioning security in Apex answers is a visible gap; the classic probe is "a user with Read access can't see data returned by your Apex class — why?" and its twin "why can a user see a field in the UI but not in Apex?". The senior answer always has the shape: name the layer that failed → name the keyword/tool that fixes it → say the one-liner. Every incident in this module is a real org where someone forgot that Apex's default is no filter:

  1. Apex defaults to system mode (no sharing, no FLS) — the keyword is the fix.
  2. CRUD/FLS enforcement is one-liner territory in 2026 (stripInaccessible, WITH USER_MODE) — know both.
  3. Sharing is a chain (OWD → hierarchy → rules → manual) — know where each link sits.
  4. Injection defense is bind-variable discipline + whitelisting.
  5. Endpoints and integrations need the same discipline on the inbound side.
  6. Audit surfaces prove what happened; Event Monitoring proves who saw.

By the end of this module, "knowing it" looks like this: given any one of the 9 problems below, you can (a) name the layer that failed, (b) explain the fix on a whiteboard, (c) write the corrected class/design from memory, and (d) say which interview question it maps to.

#IncidentThe villain mechanism
1The Search That Returned EverythingSystem mode — Apex without sharing
2The Field the Rep Shouldn't Have SeenCRUD/FLS — no enforcement in queries
3The Search Box That Ran Your SOQLSOQL injection via dynamic queries
4The Nightly Job That Leaked Private DataBatch in system mode + the OWD chain
5The Hierarchy That Opened Every DoorRole hierarchy + "Grant Access Using Hierarchies"
6The Permission Set That Became a CrownProfiles vs permission sets vs custom permissions
7The Endpoint That Served Everyone's DataUnsecured REST endpoint
8The Leak Nobody Could TraceAudit surfaces — who saw what
9🏆 Capstone — The Org Where One User Saw EverythingThe multi-symptom security incident

Protocol reminder (from file 00): attempt in writing FIRST (≥2 hypotheses + 2 solution attempts), hard 45-min cap, hint ladder, then reveal, then REDO, then retrieval drill. The sealed answer sheet lives in 05b_Topic05_Security_Sharing_Answer_Sheet.md. You are expected to fail. The failure is the task.


INCIDENT 1 — THE SEARCH THAT RETURNED EVERYTHING

STAKES

The org is Private on Accounts. A rep opens the new LWC "Account Search" — a search box wired to an Apex class — types a competitor's name, and sees 40 Accounts the rep has no access to: records owned by other teams, records the rep's role shouldn't know exist. The rep isn't malicious; they're just curious. The class "looks fine" — it's a standard public class AccountSearch { public static List<Account> find(String q) { return [SELECT Id, Name, OwnerId FROM Account WHERE Name LIKE :q]; } }. The dev's defense: "But the org is Private — sharing is enforced by the platform."

THE INCIDENT

// The search class — "sharing is enforced by the platform":
public class AccountSearch {
    public static List<Account> find(String q) {
        return [SELECT Id, Name, OwnerId FROM Account WHERE Name LIKE :q];
    }
}
// Called by an LWC via @AuraEnabled

THE PROBLEM

"The org is Private, so sharing must be enforced" — why is that wrong for Apex? Name the default execution context, the three sharing keywords and what each does, and the fix (keyword + why inherited sharing is the modern default). Then the follow-up: does the fix alone fully protect this search — what SECOND layer is still missing?

Write: (1) the context trap, (2) the three keywords, (3) the fix + the second missing layer.


HINT LADDER

  • Hint 1 (the avenue): (1) Apex classes run in system mode by default — no sharing enforcement, no FLS. OWD/Private applies to the UI and to Apex only when the class declares it. (2) Keywords: with sharing, without sharing, inherited sharing (API 45+). (3) The second layer: even with sharing doesn't check field-level access — the search returns fields the rep can't read (CRUD/FLS) → the 2026 fix is Security.stripInaccessible / WITH USER_MODE.
  • Hint 2 (the mechanism): (1) In a Private org the UI filters; Apex without a keyword bypasses the filter — the class returned 40 Accounts because it never asked the platform to enforce sharing. with sharing enforces the running user's record-level access; without sharing bypasses it (privileged service classes — a security-review flag); inherited sharing (API 45+) inherits the caller's mode — the recommended default because it prevents accidental over/under-sharing. (2) Even with sharing on, the returned fields are unfiltered — FLS is a separate filter. The complete fix: with sharing + Security.stripInaccessible(AccessType.READ, records) (API 50+) or WITH USER_MODE on the query (API 59+).
  • Hint 3 (the skeleton): Fix: public inherited sharing class AccountSearch { @AuraEnabled public static List<Account> find(String q) { return Security.stripInaccessible(AccessType.READ, [SELECT ... FROM Account WHERE Name LIKE :q]).getRecords(); } } — or ... WHERE Name LIKE :q WITH USER_MODE. Second layer: FLS (fields), plus third: validate input length, paginate, expose only what the UI renders.

THE REVEAL — POSTMORTEM

What actually happened (real class of incidents — the leaked search; agent 05's scenario #2 verbatim: "a user with Read access to an object can't see data returned by your Apex class — why?"):

The context trap: Salesforce enforces sharing for the UI and for Apex only when the class says so. A class with no sharing keyword runs in SYSTEM MODE — record access AND field access filters are off. In a Private org, [SELECT ... FROM Account] from a no-keyword class returns every Account. The dev's defense ("the org is Private") conflates UI enforcement with Apex enforcement — the exact gap the interview probes. The fix is never "trust the org"; it's "declare the context."

The three keywords (memorize — the #1 security probe):

  • with sharing — enforces the running user's sharing rules (record-level access). When the class is called from an LWC/trigger, the running user is the user.
  • without sharing — bypasses sharing (privileged maintenance/service classes — e.g., a nightly job that must touch all records). Security-review flag; every use needs a justification.
  • inherited sharing (API 45+) — inherits the caller's sharing mode: user-context callers get user filtering; system/job callers keep system access. The recommended default — prevents accidentally over-sharing (without sharing) and accidentally under-sharing (a with sharing class called by a privileged flow that then fails silently on data it can't see).

The complete fix (two layers, not one): with sharing/inherited sharing fixes record access; it does NOT fix field access. The 2026 one-liners:

// Layer 1 — record access: the keyword
public inherited sharing class AccountSearch { ... }
// Layer 2 — field access: strip what the user can't read
return Security.stripInaccessible(AccessType.READ, results).getRecords();
// ...or in the query itself (API 59+):
[SELECT Id, Name FROM Account WHERE Name LIKE :q WITH USER_MODE]

And the hygiene layer: return only the fields the UI renders (never OwnerId/internal fields "just in case"), validate + truncate the input, paginate.

Why the "obvious fixes" failed (the contrast):

  • "Make the org less private" → destroying the baseline to fix one class — backwards.
  • "Add with sharing only" → fixes record access; the fields still leak (FLS is a separate filter) — half the answer, and the interviewer will push on FLS.
  • "It's only shown to reps" → "only" is the leak's middle name; access decisions are per-user, per-field, never per-role-assumption.

KNOWLEDGE EXTRACTION (interview-ready)

  • "A user can't see data returned by your Apex class — why?" → The class runs in system mode (no sharing keyword, or without sharing) → no record-level or field-level enforcement. Fix: with sharing/inherited sharing + FLS enforcement (Security.stripInaccessible or WITH USER_MODE).
  • "The three sharing keywords?"with sharing (enforce running user's record access), without sharing (bypass — privileged jobs), inherited sharing (API 45+, inherit caller's mode — the recommended default).
  • "Why can a user see a field in the UI but not in Apex?" → Apex runs in system mode by default for CRUD/FLS — the code doesn't enforce field-level security. Fix: isAccessible() checks, Security.stripInaccessible (API 50+), or WITH USER_MODE (API 59+). Always mention the fix with the cause.
  • "What is Security.stripInaccessible?" → API 50+; strips fields the user can't access (READ/UPDATE) from a query result; returns StripInaccessibleResult (records + removed fields). The bulk-safe FLS enforcer.

THE REDO

From memory: the context trap (one line), the three keywords with one-liners, and the two-layer fix (keyword + stripInaccessible/USER_MODE) with the exact code.

RETRIEVAL DRILL

  1. What is the default execution context of a no-keyword Apex class?
  2. The three sharing keywords — what each does, and the modern default.
  3. Why does with sharing NOT fully protect a query?
  4. Security.stripInaccessible — what it returns and which API.
  5. WITH USER_MODE vs WITH SYSTEM_MODE.

INTERVIEW MAPPING

Agent 05's scenario #2 verbatim — the single most asked security probe at 3–4yr. The complete answer has the shape: cause (system mode) → fix (keyword) → second layer (FLS one-liners) → hygiene (field minimization). Juniors stop after "with sharing"; seniors volunteer FLS.


INCIDENT 2 — THE FIELD THE REP SHOULDN'T HAVE SEEN

STAKES

A custom object Candidate__c has Salary_Expectation__c, SSN__c, Internal_Notes__c — fields that should be visible only to HR. A new "Candidate Overview" Aura component calls an Apex class and renders the record. The component checks nothing. HR profile is fine. But a recruiter (profile without FLS on those fields) opens the overview and sees Salary and SSN rendered in the browser. The browser shows data the profile denies — because the class returned it, and the component rendered it. The admin's response: "But the recruiter's profile doesn't have access to those fields — the UI hides them."

THE INCIDENT

// The overview class — "the UI hides restricted fields":
public with sharing class CandidateOverview {
    @AuraEnabled
    public static Candidate__c get(String id) {
        return [SELECT Id, Name, Salary_Expectation__c, SSN__c, Internal_Notes__c
                FROM Candidate__c WHERE Id = :id];   // ← all fields, no FLS check
    }
}
// The component renders the full record — including denied fields.

THE PROBLEM

"The profile denies the fields, so they're hidden" — which filter does that statement assume, and why is it false for Apex? Name the FLS enforcement hierarchy: the describe-based check, the bulk-safe one-liner, and the query-time one-liner (with the API versions). Then the component-side question: should the Apex layer return restricted fields at all — or should it return nothing and let the UI decide?

Write: (1) the false assumption, (2) the three enforcement mechanisms (code + API), (3) the defense-in-depth answer.


HINT LADDER

  • Hint 1 (the avenue): (1) FLS is enforced by the UI (page layouts, field-level security) — Apex returns whatever the query selects; with sharing does NOT filter fields. (2) Mechanisms: Schema.getGlobalDescribe() + isAccessible() per field (verbose); Security.stripInaccessible(AccessType.READ, records) (API 50+ — bulk-safe, strips denied fields, returns StripInaccessibleResult); WITH USER_MODE on the SOQL (API 59+ — enforces CRUD/FLS at query time). (3) Defense-in-depth: the class returns ONLY the fields the UI needs; the component renders only what it received.
  • Hint 2 (the mechanism): (1) The profile's FLS denies the fields in the UI — page layout + field-level security decide what the browser shows for standard components. An Apex class selecting those fields returns them regardless — the component then renders them because the component doesn't re-check. (2) Schema.SObjectType.Candidate__c.fields.SSN__c.isAccessible() = the granular check (per field — verbose at scale); Security.stripInaccessible(AccessType.READ, records).getRecords() = the bulk one-liner that removes denied fields (the 2026-standard answer); WITH USER_MODE = enforcement inside the query (single line, no post-processing). (3) The senior design: minimal projection (never SELECT sensitive fields "just in case"); sensitive fields go through a dedicated endpoint with explicit permission checks (Custom Permission, FeatureManagement.checkPermission).
  • Hint 3 (the skeleton): Fix: public with sharing class ... { return Security.stripInaccessible(AccessType.READ, [SELECT ... FROM Candidate__c WHERE Id = :id]).getRecords(); } — denied fields come back stripped (null) → the component has nothing to render. Even better: minimal SELECT — only the fields the UI needs.

THE REVEAL — POSTMORTEM

What actually happened (real class of incidents — the FLS leak; agent 05's "user can see in UI but not in Apex" twin, reversed):

The false assumption: FLS (field-level security) is enforced by the UI layer (page layouts + field security decide what standard pages render) — NOT by Apex. A class selecting restricted fields returns them; a custom component renders whatever it received. with sharing is record-access only — fields are a separate filter that Apex bypasses by default. The recruiter saw Salary and SSN because the class asked for them and the component rendered the payload.

The FLS enforcement hierarchy (three levels — know all three):

  1. Describe-based checks (the granular tool): Schema.SObjectType.Candidate__c.fields.SSN__c.isAccessible() — per-field, explicit, verbose; correct when a few fields matter.
  2. Security.stripInaccessible(AccessType.READ, records) (API 50+ — the bulk-safe one-liner): strips denied fields from query results in one call; returns StripInaccessibleResult (getRecords() + getRemovedFields()). The 2026-standard answer for "enforce FLS across a whole query."
  3. WITH USER_MODE (API 59+ — enforcement at query time): [SELECT ... FROM Candidate__c WHERE Id = :id WITH USER_MODE] — CRUD/FLS enforced inside the query; no post-processing; the modern single-line favorite. (WITH SYSTEM_MODE = the default bypass, explicit.)

The defense-in-depth answer (say it like a senior): (1) Minimal projection — the class selects ONLY the fields the UI legitimately renders for the caller's role; sensitive fields (SSN, salary) are never in a generic query "just in case." (2) FLS enforcementstripInaccessible/USER_MODE as the belt. (3) UI never renders what wasn't sent — a component that renders raw payloads is a second leak path; render fields explicitly. (4) Dedicated sensitive endpoints — explicit permission gates (Custom Permission + FeatureManagement.checkPermission()) for anything genuinely sensitive — never implicit "the UI will hide it."

Why the "obvious fixes" failed (the contrast):

  • "Remove the fields from the page layout" → layout hides standard UI; the class + component bypass layouts entirely.
  • "Use with sharing" → record access, not field access — the recruiter had record access; the leak is FLS.
  • "Check isAccessible() only for SSN" → works, but it's the verbose path; the interview wants stripInaccessible/USER_MODE as the scale answer — and the minimal-projection habit as the design answer.

KNOWLEDGE EXTRACTION (interview-ready)

  • "Does with sharing enforce FLS?" → No — it enforces record-level access. FLS is a separate filter Apex bypasses by default; enforce with isAccessible() checks, Security.stripInaccessible (API 50+), or WITH USER_MODE (API 59+).
  • "The 2026 FLS one-liners?"Security.stripInaccessible(AccessType.READ, records).getRecords() (strips denied fields post-query) and ... WITH USER_MODE (enforces at query time).
  • "How do you protect sensitive fields in Apex?" → Minimal projection, FLS enforcement, explicit permission gates (Custom Permission) for sensitive endpoints, and UI rendering only what was sent.
  • "CRUD checks?"isAccessible()/isCreatable()/isUpdatable()/isDeletable() on the SObjectType — object-level; FLS on fields; Security.stripInaccessible covers both for results.

THE REDO

From memory: the false assumption, the three enforcement levels (code + APIs), and the defense-in-depth answer (4 points).

RETRIEVAL DRILL

  1. Who enforces FLS in standard UI — and who doesn't in Apex?
  2. The three FLS enforcement mechanisms (with API versions).
  3. What does StripInaccessibleResult give you?
  4. WITH USER_MODE vs WITH SYSTEM_MODE — the one-liners.
  5. The minimal-projection rule — and the dedicated-endpoint rule.

INTERVIEW MAPPING

The FLS probe (agent 05: "why can a user see a field in the UI but not in Apex?" + reversed). The three-level answer (describe / stripInaccessible / USER_MODE) + defense-in-depth is the complete 3–4yr response; naming only isAccessible() is the junior version.


INCIDENT 3 — THE SEARCH BOX THAT RAN YOUR SOQL

STAKES

A public-facing search page ("Find a store near you") calls an Apex class with a dynamic SOQL built by string concatenation. A security researcher posts a payload in the search box. Two weeks later, an interview (from a breach investigation): "Did the org ship a SOQL injection?" The class: String q = 'SELECT Id, Name FROM Account WHERE Name LIKE \'%' + input + '%\'';. The payload %' OR Id != '' OR Name LIKE '% turns the filter into a tautology → every record returned, including fields never intended for the public page. The audit trail shows the query ran 14,000 times in one hour (a bot hammering the endpoint).

THE INCIDENT

// The vulnerable class — string concatenation with user input:
public with sharing class StoreSearch {
    public static List<Account> find(String input) {
        String q = 'SELECT Id, Name FROM Account WHERE Name LIKE \'%' + input + '%\'';
        return Database.query(q);     // ← injection point
    }
}
// Payload:  %' OR Id != '' OR Name LIKE '%
// Result:   WHERE Name LIKE '%%' OR Id != '' OR Name LIKE '%%'   → matches EVERYTHING

THE PROBLEM

Reconstruct the injected query (show the final SOQL), name the three defense layers (with the exact tools), and answer the follow-ups: why is escapeSingleQuotes NOT a complete defense, and what does whitelisting protect that binding can't?

Write: (1) the reconstructed query, (2) the three defenses, (3) the two follow-up answers.


HINT LADDER

  • Hint 1 (the avenue): (1) The concatenation turns the payload into a tautology: WHERE Name LIKE '%%' OR Id != '' OR Name LIKE '%%' → every row matches. (2) Defenses: bind variables (the primary), escapeSingleQuotes (legacy/dynamic strings), whitelisting for dynamic object/field names. (3) escapeSingleQuotes only escapes ' — it does NOT protect against injection when the field/object name is dynamic (binding protects values, never names).
  • Hint 2 (the mechanism): (1) Injected SOQL: the payload closes the literal, adds OR Id != '', re-opens — final query matches all rows. (2) Layers: (a) bind variablesWHERE Name LIKE :pattern; the value is bound, never parsed as syntax — kills the injection class for VALUES; (b) escapeSingleQuotes — for legacy dynamic strings where binding isn't possible: String.escapeSingleQuotes(input); (c) whitelisting — dynamic object/field names (e.g., sortable columns) can't be bound — validate against Schema describe / a hardcoded allowlist before concatenating. (3) Follow-ups: escaping is a patch on concatenation; binding removes concatenation. Whitelisting protects NAMES — you can't bind ORDER BY columns or SELECT fields — so names need allowlists, never user input.
  • Hint 3 (the skeleton): (1) WHERE Name LIKE '%%' OR Id != '' OR Name LIKE '%%' — tautology, all rows. (2) (a) bind: String pat = '%' + input + '%'; ... WHERE Name LIKE :pat — bind the VALUE; (b) escapeSingleQuotes for unavoidable dynamic strings; (c) whitelist names: Set<String> allowed = new Set<String>{'Name','City','Zip'}; if (!allowed.contains(col)) throw .... (3) escapeSingleQuotes ≠ complete (escapes quotes, not the concatenation pattern; binding is the real fix); whitelisting covers names (binding can't).

THE REVEAL — POSTMORTEM

What actually happened (real class of incidents — SOQL injection; agent 05's Q: "how do you prevent SOQL injection?" — the interview's #1 security-code question):

The reconstructed query: the payload %' OR Id != '' OR Name LIKE '% concatenated into ... WHERE Name LIKE '%%' OR Id != '' OR Name LIKE '%%' — a tautology: every row matches the filter. The "search" became "SELECT everything." (The researcher's second step, in a real breach: inject metadata-query style via dynamic DML if the code had Database. calls — the point is the same: user input became query syntax.)

The three defense layers (memorize the exact tools):

  1. Bind variables (the primary defense): String pattern = '%' + input + '%'; [SELECT Id, Name FROM Account WHERE Name LIKE :pattern] — the value is bound, never parsed as syntax. Kills the entire injection class for values; always the default.
  2. String.escapeSingleQuotes(input) — for legacy dynamic queries where binding isn't possible. Escapes '\'. A patch, not a design; never the first choice.
  3. Whitelisting for dynamic names: object/field names in Database.query strings (e.g., ORDER BY <column>, dynamic field selection) cannot be bound — validate against Schema describe or a hardcoded allowlist (Set<String> allowed = new Set<String>{'Name','City'}; if (!allowed.contains(col)) return error;). User input is never a name.

The two follow-ups (the senior differentiators):

  • "Why isn't escapeSingleQuotes complete?" → Escaping is a patch on the concatenation pattern; it protects a quote character in a literal, but the pattern itself (user input inside a query string) remains the hole — and attackers target other injection surfaces (operators, numeric contexts, unicode quirks in legacy parsers). The real fix is removing concatenation (binding); escaping only exists for unavoidable dynamic strings. Interviewer translation: "don't sanitize, restructure."
  • "What does whitelisting protect that binding can't?"Names. Bind variables can only carry values (LIKE :pattern); you cannot bind a field name, an object name, or an ORDER BY column. Anything structural derived from user input must come from an allowlist (or describe validation) — never from the request.

Why the "obvious fixes" failed (the contrast):

  • "Just escape the quotes" → the patch-vs-design distinction; the class still concatenates user input into a query string.
  • "Make the search private" → the injection is input trust, not sharing; the public page is a feature, the concatenation is the bug.
  • "Validate the input format" → format validation doesn't close the syntax hole; binding closes it.

KNOWLEDGE EXTRACTION (interview-ready)

  • "How do you prevent SOQL injection?" → Bind variables for values; escapeSingleQuotes for unavoidable dynamic strings; whitelist/describe-validate every dynamic object/field name; never concatenate user input into a query.
  • "Why bind variables?" → The value is bound, not parsed as syntax — the injection class dies with the concatenation.
  • "Can you bind a field name?" → No — names need allowlists (Schema describe / hardcoded sets); binding carries values only.
  • "Dynamic SOQL you must still write?" → Sortable columns, dynamic filters — keep the structure static, bind the values, whitelist the names.

THE REDO

From memory: the reconstructed tautology query, the three defenses with exact tools, and the two follow-up answers.

RETRIEVAL DRILL

  1. Write the injected query for the payload %' OR Id != '' OR Name LIKE '%.
  2. The three defense layers, in order.
  3. Why does escapeSingleQuotes fail as a complete defense?
  4. What can't bind variables carry — and what replaces them there?
  5. The one-line rule for user input in SOQL.

INTERVIEW MAPPING

Agent 05's injection question — the security-code question asked in every loop. The senior answer adds the "patch vs design" follow-up unprompted.


INCIDENT 4 — THE NIGHTLY JOB THAT LEAKED PRIVATE DATA

STAKES

OWD on Deal__c is Private. A nightly Batch Apex job aggregates deals into a weekly "Team Performance Summary" object — a dashboard object that (per the spec) only managers should see. After the first month, a rep opens the summary and sees other teams' revenue figures. The batch "ran as the system." The class: public class DealSummaryBatch implements Database.Batchable<SObject> { ... }no sharing keyword. The dashboard object's OWD is Public Read. The batch inserted aggregated rows for every team into it.

THE INCIDENT

// The batch — "it's a batch job, it just runs":
public class DealSummaryBatch implements Database.Batchable<SObject> {
    public Database.QueryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator([SELECT OwnerId, Amount FROM Deal__c]); // ← system mode
    }
    public void execute(Database.BatchableContext bc, List<Deal__c> scope) {
        // aggregate per owner → upsert Team_Summary__c (Public Read object)  ← the leak
    }
}
// Chain of failures: no keyword (system mode) + aggregated data written to a Public Read object

THE PROBLEM

Trace the leak chain (the keyword, the aggregation, the destination object's OWD — name all three failures), explain the correct OWD→sharing chain (the full model: OWD → role hierarchy → sharing rules → manual/Apex sharing — one line each), and design the corrected job: keyword policy for batch (with/without/inherited + when each is right), the destination design (Public Read is the anti-pattern for sensitive aggregates), and the defense-in-depth check.

Write: (1) the three-link chain, (2) the sharing model chain, (3) the corrected design.


HINT LADDER

  • Hint 1 (the avenue): (1) Chain: no sharing keyword → system mode reads ALL deals (fine for the job) BUT the output — aggregated revenue — landed on a Public Read object → every rep sees it. The leak is at the destination, not the source. (2) Sharing chain: OWD (baseline) → role hierarchy (managers see below) → sharing rules (declarative group extensions) → manual/Apex sharing (per-record grants). (3) Corrected: destination object's OWD must be Private with sharing rules to managers; keyword policy per job class; defense-in-depth checks on the output.
  • Hint 2 (the mechanism): (1) Batch classes: with no keyword → system mode (all records, all fields). That's sometimes the intent (a summary job needs all deals) — the failure is writing the result into a Public Read object: the summary inherits the destination's OWD, not the source's. The three links: system-mode read (intended), un-keyworded class (sloppy), Public Read destination (the actual leak). (2) The chain: OWD sets the baseline per object; role hierarchy adds manager visibility (unless "Grant Access Using Hierarchies" is disabled); sharing rules extend to groups (criteria/owner-based); manual/Apex sharing grants per-record (Deal__Share rows, managed sharing in Apex). (3) Corrected: destination OWD Private + sharing rule (owner-based: managers of the team) or Apex managed sharing; class keyword policy: without sharing ONLY when the job genuinely needs all data AND the output is access-controlled (or inherited sharing with a privileged caller); defense-in-depth: read-back test (rep-context query on the summary returns zero rows).
  • Hint 3 (the skeleton): Corrected: Team_Summary__c OWD = Private; sharing rule: "Team Managers" group gets Read (owner-based); the batch may stay without sharing (justified: needs all deals) but the output object is access-controlled — the class keyword alone can't fix a Public destination. Policy statement: "System-mode jobs are fine when the output lands in access-controlled objects; the leak is never the source, it's the destination."

THE REVEAL — POSTMORTEM

What actually happened (real class of incidents — the aggregate dashboard leak; the "batch job" as the security-blind spot):

The three-link chain (trace it exactly like this):

  1. No sharing keyword → system mode: the batch reads ALL Deal__c records. For a summary job that's usually intended (it must aggregate across teams).
  2. Un-keyworded class = undocumented privilege: no keyword means nobody made a decision — the job silently got system access. (If it needed it, say without sharing with a comment; if not, inherited sharing.)
  3. The actual leak — the destination: Team_Summary__c OWD = Public Read. The aggregation wrote every team's revenue into an object every rep can read. The summary inherits the destination's OWD, not the source's. This is the classic "the leak is the destination" trap: security reviewers audit the read path (the batch's query) and miss the write path (where the data lands).

The sharing model chain (memorize — the #1 sharing question): OWD (per-object baseline: Private / Public Read Only / Public Read-Write / Controlled by Parent) → role hierarchy (managers gain access to subordinates' records when "Grant Access Using Hierarchies" is enabled for the object) → sharing rules (declarative, group-based extensions: criteria-based or owner-based, without changing ownership) → manual/Apex sharing (per-record grants; API: Deal__Share rows; Apex managed sharing for programmatic rules). The chain is cumulative: each link adds access above the baseline.

The corrected design (three parts):

  1. Destination security: Team_Summary__c OWD → Private; a sharing rule grants the "Team Managers" group Read (owner-based: summary rows owned by the team's manager). The object now controls visibility — the data can't leak regardless of who computed it.
  2. Keyword policy for batch: without sharing ONLY with a justification comment (the job needs all deals) — and the understanding that system access is safe only when the output is access-controlled; otherwise inherited sharing. The rule: "System-mode reads are fine; system-mode writes into public objects are the breach."
  3. Defense-in-depth: an aggregation-time check (the summary rows are written with the correct owner per team — owner-based rules depend on ownership), and a read-back test: a rep-context query on the summary returns zero rows.

Why the "obvious fixes" failed (the contrast):

  • "Add with sharing to the batch" → the batch needs all deals to aggregate — with sharing would silently under-report (the under-sharing bug from Incident 1). The leak is the destination, not the read.
  • "Make the batch without sharing properly" → documents the intent but changes nothing about the Public Read object.
  • "Remove the dashboard object" → the feature is real; the fix is Private + sharing rules, not feature removal.

KNOWLEDGE EXTRACTION (interview-ready)

  • "Explain the sharing model: OWD, sharing rules, manual sharing." → OWD = baseline per object (Private/Public Read Only/Public Read-Write/Controlled by Parent); role hierarchy adds manager access (when enabled); sharing rules extend to groups declaratively; manual/Apex sharing grants per-record. Chain: OWD → hierarchy → rules → manual.
  • "What context do batch/scheduled jobs run in?" → System mode by default (no keyword / without sharing); with sharing enforces the running user. Jobs needing all data are legitimate — but the output must be access-controlled.
  • "Apex managed sharing?" → Programmatic per-record grants (Deal__Share rows) for rules the declarative model can't express; the code owns the grant lifecycle.
  • "Sharing rule vs manual share?" → Sharing rules: declarative, group-based, automatic extensions of OWD. Manual shares: per-record user grants (UI or Apex __Share rows).

THE REDO

From memory: the three-link chain, the sharing model chain (4 links, one line each), and the corrected design (destination Private + rule, keyword policy, read-back test).

RETRIEVAL DRILL

  1. What context does an un-keyworded batch run in?
  2. Where was the actual leak — and why do reviewers miss it?
  3. The sharing chain: 4 links, one line each.
  4. When is without sharing legitimate?
  5. Sharing rule vs manual share — one line each.

INTERVIEW MAPPING

Agent 05's sharing-model Q&A ("OWD, sharing rules, manual sharing") + the batch-context probe. The "leak is the destination" insight is the senior differentiator — it connects Module 1's batch knowledge to security.


INCIDENT 5 — THE HIERARCHY THAT OPENED EVERY DOOR

STAKES

Deal__c is Private. A sales director (high in the role hierarchy) is downsized — but not before their team notices: the director could see every deal in the entire org, including deals owned by other directors' teams and deals explicitly marked "Confidential — Directors only" (a picklist field, used with a sharing rule that grants access to everyone above a certain level). The org "is private." A VP asks: "Why did the hierarchy open everything?" The answer involves the role hierarchy setting, the ownership of the records, and a criteria-based sharing rule with an over-broad condition.

THE INCIDENT

Deal__c OWD: Private
Role hierarchy: enabled for Deal__c ("Grant Access Using Hierarchies" checked)
Sharing rule: "Confidential" deals shared WITH everyone in role "Directors and above"
  → criteria: Confidential__c = TRUE → share with role group "Directors+"
Symptom: a director sees ALL deals — not just subordinates' or "Confidential" ones.

THE PROBLEM

Trace the access math: why does the director see everything? Name the four access sources that combine (with one line each), the two settings that control the hierarchy, and when the hierarchy is bypassed. Then the fix: how do you make "Confidential" ACTUALLY confidential in a hierarchy-enabled org?

Write: (1) the access math, (2) the hierarchy mechanics + bypasses, (3) the confidential-data design.


HINT LADDER

  • Hint 1 (the avenue): (1) The director sees everything because: hierarchy grants access to every record owned below in the tree — and "below" in a mis-arranged hierarchy includes whole teams; PLUS the criteria-based sharing rule grants ALL "Confidential" records to ALL directors+; PLUS manual shares. Four sources: OWD baseline, hierarchy, sharing rules, manual/Apex shares. (2) Hierarchy mechanics: "Grant Access Using Hierarchies" per object; bypassed when the class runs without sharing; also user "View All Data". (3) The fix: confidential data needs the hierarchy off for that object (or a separate private object), sharing rules scoped to specific people (not roles "and above"), and — the sharp answer — the "Confidential" flag is data, not access: a criteria rule reading the flag can't prevent the hierarchy from seeing the record anyway.
  • Hint 2 (the mechanism): (1) Access math: Director D sees Deal X if ANY source grants access: (a) OWD Private grants owner + those above via hierarchy (role tree position: if the director's role is above the owner's role → access, regardless of team semantics — roles are a tree, teams are a fiction); (b) sharing rule grants all Confidential deals to the "Directors+" group → every director sees every confidential deal; (c) manual/Apex shares (unlikely here); (d) ownership — records owned by the director or their subordinates. The "confidential" picklist is NOT an access control — it's a field; criteria rules grant, never deny; the hierarchy ignores it. (2) Hierarchy: enabled per object ("Grant Access Using Hierarchies"); when disabled → managers don't inherit. Bypasses: without sharing Apex, users with "View All Data"/"Modify All Data", and API with system context. (3) Confidential design: for genuine confidentiality, the object's hierarchy must be OFF (or use a separate object with OWD Private + hierarchy off), sharing rules scoped to named users/groups — never "roles and above" — and access granted by explicit share, never by a flag field a rule reads.
  • Hint 3 (the skeleton): (1) Sources: OWD + hierarchy + sharing rules + manual shares; the director's visibility = hierarchy (all below in role tree) + the Directors+ rule (all Confidential). (2) Hierarchy: "Grant Access Using Hierarchies" checkbox per object; bypassed by without sharing / View All Data / system context. (3) Confidential: hierarchy OFF on the object (or dedicated object), rules to specific groups, explicit grants; the picklist is data — access is structure.

THE REVEAL — POSTMORTEM

What actually happened (real class of incidents — the "Private but everyone sees everything" org; the hierarchy-misconception org):

The access math (four sources, one line each — memorize): A user can see a record if any of these grant access: (1) OWD — the baseline (Private here); (2) role hierarchy — everyone above the owner in the role tree inherits access (when enabled); (3) sharing rules — declarative grants to groups (criteria/owner-based); (4) manual/Apex sharing — per-record grants. The director saw everything because: the hierarchy granted every record owned below the director's role position — and roles are a tree, not a team chart: mis-arranged roles make "below" include other teams' records; PLUS the criteria-based rule granted all Confidential deals to all Directors+ — making the "confidential" marker grant MORE access, not less. The picklist is data, not access: criteria rules can only grant; they can never deny; and the hierarchy never reads the flag.

Hierarchy mechanics + bypasses (the Q&A): The hierarchy is per-object ("Grant Access Using Hierarchies" checkbox in OWD); when checked, managers gain access to records owned by anyone below them; when unchecked, the hierarchy is off for that object. Bypassed by: without sharing Apex classes, users with "View All Data"/"Modify All Data", and system-mode API contexts. Interview one-liner: "Role hierarchy grants managers visibility of their subordinates' records for Private objects — unless the object disables it, or the code runs without sharing."

The confidential-data design (the senior fix): (1) Confidentiality needs the hierarchy OFF for that object (or a dedicated confidential object with Private OWD + hierarchy disabled) — the hierarchy is the hole that turns "directors only" into "everyone above the owner." (2) Sharing rules scoped to people, not "roles and above": grant to a named group of directors, never a "Directors+" role group whose membership grows with the role tree. (3) Access is structure, not data: the "Confidential" flag is a field — rules can read it to grant, but nothing reads it to deny. Genuine confidentiality = Private OWD + hierarchy off + explicit grants; the flag is only for display/process logic, never the access control.

Why the "obvious fixes" failed (the contrast):

  • "Set OWD to Private" → it WAS private; the hierarchy + the rule are the grants.
  • "Use the flag to restrict" → the flag is data; criteria rules grant, never deny — the flag made it MORE visible.
  • "Fix the role tree" → helps, but the "Directors+" rule would still grant every confidential deal to every director; and confidential needs hierarchy off, not a cleaner tree.

KNOWLEDGE EXTRACTION (interview-ready)

  • "Role hierarchy and when is it bypassed?" → Grants managers access to subordinates' records for Private OWD when "Grant Access Using Hierarchies" is enabled; bypassed by without sharing classes, "View All Data" users, and system-mode API. Disabled per object.
  • "Sharing rule vs manual share?" → Sharing rules: declarative, group-based, automatic extensions of OWD. Manual shares: per-record user grants (UI or Apex __Share rows), often Apex-managed.
  • "How do you make data truly confidential?" → Private OWD + hierarchy off (or dedicated object) + scoped grants to named groups + explicit shares; access is structure — flags are data.
  • "Criteria-based sharing rules can only do what?" → Grant. They read a condition to ADD access; they never remove or deny.

THE REDO

From memory: the four access sources, the hierarchy mechanics + bypasses, and the confidential-data design (3 points).

RETRIEVAL DRILL

  1. The four access sources that combine (one line each).
  2. What controls the hierarchy per object — and its bypasses?
  3. Why does a criteria rule on a "Confidential" flag make it MORE visible?
  4. The three-point confidential design.
  5. "Roles are a tree, teams are a fiction" — what does that mean?

INTERVIEW MAPPING

Agent 05's role-hierarchy Q&A ("what is Role Hierarchy and when is it bypassed?") + the sharing-rule trap. The "flag is data, access is structure" insight is the senior answer.


INCIDENT 6 — THE PERMISSION SET THAT BECAME A CROWN

STAKES

A custom permission Manage_Compensation__c was created for the HR app. To "make it easy," the admin assigned it to a Permission Set Group that "everyone in the company" uses. Months later: a fired HR specialist's access review shows the permission set group is still assigned to 400 users, the custom permission is checked in Apex as FeatureManagement.checkPermission('Manage_Compensation__c'), and one junior developer "helpfully" added the same permission set to a role-based public group. The org has: profiles (fat, legacy, with everything), permission sets (duplicated across roles), and custom permissions whose checks nobody audits. An auditor asks the architecture question.

THE INCIDENT

Profiles: 9 legacy profiles, each with object/field access duplicated by hand (the "fat profile" era)
Permission sets: 23 — several overlapping ("Sales Extended", "Sales Extended v2", "Sales LWC Access")
Permission set groups: 1 ("Everyone") — includes Manage_Compensation__c permission set
Custom permission: Manage_Compensation__c — checked in Apex via FeatureManagement.checkPermission()
Symptom: 400 users have the HR custom permission; nobody knows why; audits fail.

THE PROBLEM

Name the four access entities and the modern best practice (lean profiles + permission sets + groups), the danger of the "Everyone" group, and the governance design: where does the custom permission check belong, how do you audit it, and what is the correct assignment model (when do you use each entity)?

Write: (1) the four entities + the modern practice, (2) the "Everyone" failure, (3) the governance design.


HINT LADDER

  • Hint 1 (the avenue): (1) Entities: profiles (base licenses + baseline access — lean by design), permission sets (granular additions without changing the profile), permission set groups (bundles for easy assignment), custom permissions (app-defined flags checked in Apex). Modern practice: lean profiles + permission sets; groups for role-based bundles. (2) The "Everyone" group: a bundle with a sensitive custom permission becomes a de facto crown — assigned widely, audited never; the custom permission's check is code, its assignment is the blast radius. (3) Governance: custom permission = capability, not role; assignment by group with justified membership; audit via permission-set assignment reports + FeatureManagement.checkPermission in code with logging; the Apex check is defense-in-depth, not the only control.
  • Hint 2 (the mechanism): (1) Profiles set baseline licenses + baseline object/field/class access (legacy: assigned to users); permission sets add granular access (object, field, record type, app, Apex class) without touching the profile — the modern best practice is lean profile + permission sets; custom permissions are app-defined flags checked in Apex (FeatureManagement.checkPermission()) — invisible to declarative UI except via assignment; permission set groups bundle permission sets for role-based assignment. (2) The failure: bundling a sensitive custom permission into an "Everyone" group inverts the model — the capability leaks to 400 users; the "junior added the permission set to a public group" doubles the leak; the fat profiles mean FLS drift (Module 5 Incident 2's setup-side twin). (3) Design: custom permissions are for capability gates in code (sensitive actions), assigned to the smallest justified group; audit = permission-set-assignment report + periodic access review + logging on sensitive checks; never nest sensitive permissions in broad groups; groups are for role bundles (e.g., "Sales Rep Bundle"), reviewed quarterly.
  • Hint 3 (the skeleton): (1) Profiles = baseline; permission sets = additions; groups = bundles; custom permissions = code-checked capabilities. Lean profile + PS + groups is the modern practice. (2) "Everyone" = crown: broad assignment, no audit; the Apex check is code-side, assignment is the exposure. (3) Governance: capability ≠ role; smallest-group assignment; quarterly access reviews; permission-set assignment reports; logging around FeatureManagement.checkPermission for sensitive operations.

THE REVEAL — POSTMORTEM

What actually happened (real class of incidents — the permission-set sprawl org; agent 05's "Profiles vs Permission Sets vs Custom Permissions vs Permission Set Groups" Q&A):

The four entities (memorize — the Q&A):

  1. Profiles — set base licenses + baseline object/field/class access (legacy: directly assigned to users). The modern practice: lean profiles (minimal baseline) + permission sets for everything granular.
  2. Permission sets — grant additional granular access (object, field, record types, apps, Apex class access) without changing the profile. The modern way to give access.
  3. Permission set groups — bundle permission sets together for role-based assignment ease (e.g., "Sales Rep Bundle").
  4. Custom permissions — app-defined flags checked in Apex via FeatureManagement.checkPermission('Name') — the only one of the four that code can read at runtime.

The "Everyone" failure (the crown): bundling Manage_Compensation__c into an "Everyone" permission set group inverts the access model: a capability meant for a handful became org-wide by assignment — and because custom permissions are invisible in most UIs, nobody can see the crown until the audit. The junior's "helpful" addition of the permission set to a public group doubles the leak. The blast radius of a custom permission is its assignment, not its code; the check (FeatureManagement.checkPermission) only gates code paths — it can't stop a user who already has the flag.

The governance design (the senior answer):

  1. Capability ≠ role: custom permissions gate sensitive actions in code (view compensation, void invoices) and are assigned to the smallest justified group — never a broad/Everyone bundle.
  2. Lean profiles + permission sets + groups: baseline in the profile; granular access in permission sets; role bundles in groups; the group's membership is reviewed (quarterly access reviews).
  3. Audit surfaces: permission-set-assignment reports (who has what), group membership reports, periodic access reviews, and — for sensitive capabilities — logging around the check (FeatureManagement.checkPermission) so the audit trail shows who used the capability, not just who has it.
  4. The meta-rule: "A permission is a key, not a badge: assign the smallest set of keys to the smallest set of people, and review the keyring quarterly."

Why the "obvious fixes" failed (the contrast):

  • "Remove the custom permission from the group" → correct direction; but without the group-membership review and assignment reports, the next "helpful" addition restores it silently.
  • "Add more checks in Apex" → the check gates code paths; assignment is the exposure — code can't fix assignment.
  • "Delete the fat profiles and start over" → the right end-state, wrong first move; migrate profile grants to permission sets object-by-object with regression testing, not a big-bang.

KNOWLEDGE EXTRACTION (interview-ready)

  • "Profiles vs permission sets vs custom permissions vs groups?" → Profiles: baseline licenses + baseline access (lean by design). Permission sets: granular additions without profile changes (the modern practice). Groups: bundles for role-based assignment. Custom permissions: code-checked capability flags (FeatureManagement.checkPermission).
  • "How do you check a custom permission in Apex?"FeatureManagement.checkPermission('Permission_Name') returns Boolean — gate sensitive actions; log the check for audit.
  • "What is the modern access practice?" → Lean profiles + permission sets; permission set groups for role bundles; custom permissions for code-gated capabilities; quarterly access reviews.
  • "How do you audit access?" → Permission-set-assignment reports, group-membership reports, access reviews, SetupAuditTrail, and logging around sensitive checks.

THE REDO

From memory: the four entities (one line each), the "Everyone" failure, and the governance design (4 points).

RETRIEVAL DRILL

  1. The four access entities — one line each.
  2. Why is a sensitive custom permission in an "Everyone" group a crown?
  3. FeatureManagement.checkPermission — what does the check gate, and what can't it stop?
  4. The modern access practice in one line.
  5. Two audit surfaces for permission sprawl.

INTERVIEW MAPPING

Agent 05's profiles-vs-permission-sets Q&A — asked in every security block. The custom-permission runtime check + assignment-blast-radius insight is the senior differentiator.


INCIDENT 7 — THE ENDPOINT THAT SERVED EVERYONE'S DATA

STAKES

A "quick" REST endpoint (@RestResource) exposes account data to a partner app. The Connected App was created with the full-access scope, the class is without sharing, the endpoint doesn't check permissions ("the app is trusted"), and input is concatenated into dynamic SOQL. A security audit (and a researcher's weekend) finds: the endpoint returns every field of every Account for any valid session token — including for tokens from other apps, because the Connected App's scope grants org-wide data access and the class bypasses sharing. The partner app only needed 5 read-only fields for 1 account at a time.

THE INCIDENT

// The "trusted app" endpoint:
@RestResource(urlMapping='/partner/accounts/*')
global without sharing class PartnerAccountAPI {
    @HttpGet
    global static String get() {
        RestRequest req = RestContext.request;
        String id = req.requestURI.substringAfterLast('/');
        // No permission check. Dynamic SOQL with concatenated input:
        String q = 'SELECT Id, Name, SSN__c, Revenue__c FROM Account WHERE Id = \'' + id + '\'';
        List<SObject> recs = Database.query(q);
        return JSON.serialize(recs);   // every field, any token
    }
}

THE PROBLEM

Name the five layers that should have been in this endpoint (from agent 05's security checklist: authentication/scopes, CRUD/FLS + sharing, input validation, rate limiting, audit), and redesign it: the Connected App scopes, the class context (sharing keyword), the permission gate (which check, where), the input handling (binding + allowlist), and the response shape (minimal projection + FLS).

Write: (1) the five-layer checklist, (2) the redesign.


HINT LADDER

  • Hint 1 (the avenue): (1) Five layers: (a) auth — Connected App + scopes (least-privilege; full_access is the anti-pattern); (b) CRUD/FLS + sharing — class context + enforcement; (c) input validation — binding, no concatenation; (d) rate limiting / abuse control; (e) audit. (2) Redesign: Connected App scope = minimal (api + specific perms); class with sharing (or inherited) + Security.stripInaccessible/WITH USER_MODE; permission gate = CRUD check + custom-permission/ownership check; input = bind + validate Id format; response = DTO with only the 5 fields (never serialize raw SObjects).
  • Hint 2 (the mechanism): (1) The audit found: the Connected App's full-access scope lets any token from any app call org-wide APIs; the without sharing class bypasses record access; no FLS; concatenated input = injection (Incident 3's pattern, now inbound); no rate limit → the researcher's 14,000-requests-hour from Incident 3 is the same bot pattern. (2) Redesign layers: (a) scope — least-privilege Connected App scopes (api for REST; never full_access); (b) contextwith sharing/inherited sharing; (c) permission gateSchema.SObjectType.Account.isAccessible() + isReadable per field (or stripInaccessible on the result) + a custom-permission check for the partner capability + ownership/sharing enforced by the keyword; (d) input — bind variables + String.isId/pattern validation, allowlist the response fields; (e) response — a DTO (Id, Name, the 5 fields) — never JSON.serialize(recs) of raw SObjects (it leaks every field and every FLS gap); (f) rate limiting — Connected App request limits / API governance; (g) audit — login + API usage in Event Monitoring, SetupAuditTrail.
  • Hint 3 (the skeleton): global with sharing class ... @HttpGet ... String id = ...; 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 JSON.serialize(new PartnerDTO(recs)); — DTO = 5 fields only.

THE REVEAL — POSTMORTEM

What actually happened (real class of incidents — the trusted-app endpoint breach; agent 05's scenario #5 "expose customer data via REST API — what security considerations?"):

The five-layer checklist (memorize — the complete answer):

  1. Authentication & scopes: the Connected App is the token factory — scopes define what a token may do. Least-privilege scopes (api, specific OAuth scopes); full_access is the anti-pattern that let any app's token reach org-wide APIs. Enforce: per-app scopes + IP allowlists + token expiry.
  2. CRUD/FLS + sharing: the class context decides record/field enforcement — without sharing bypassed everything. Enforce: with sharing/inherited sharing + Security.stripInaccessible/WITH USER_MODE.
  3. Input validation: the concatenated Id = the Incident 3 injection pattern inbound. Enforce: bind variables + String.isId(id) + field allowlists.
  4. Rate limiting / abuse control: the researcher's bot hammered 14,000 requests/hour. Enforce: Connected App request limits, API governance, throttling.
  5. Audit: login history + Event Monitoring (API calls) + SetupAuditTrail — the "who called what, when" evidence.

The redesign (layer by layer):

  1. Scopes: Connected App → api scope only (no full_access); refresh-token policy tightened.
  2. Context: with sharing (or inherited sharing) — the keyword turns record access back on.
  3. Permission gate: Schema.SObjectType.Account.isAccessible() (object CRUD) + Security.stripInaccessible(AccessType.READ, ...) (field FLS) + a custom permission (Partner_Read) checked via FeatureManagement.checkPermission — the capability gate from Incident 6, now inbound.
  4. Input: bind the Id (WHERE Id = :id), validate with String.isId() / pattern, allowlist response fields.
  5. Response shape (the hidden leak): never JSON.serialize(recs) of raw SObjects — it serializes every field including SSN__c. Return a DTO (a class with exactly the 5 partner fields). The response is the last filter; the DTO is it.

Why the "obvious fixes" failed (the contrast):

  • "Just remove the SSN field from the query" → the query was the second leak; the serialization of raw SObjects is the first (it can't leak what isn't selected — but the fix must be the DTO + FLS, not field-hygiene-by-accident).
  • "The app is trusted" → trust is scopes, not intent; the token was valid for ANY app (full-access scope) — trust the scope, not the story.
  • "Add with sharing" → necessary, not sufficient: scopes, FLS, input, rate limiting, and the DTO all still missing — the five layers are the answer.

KNOWLEDGE EXTRACTION (interview-ready)

  • "Secure Apex REST endpoints?" → Connected App scopes (least privilege), class context (with sharing), CRUD/FLS checks (isAccessible + stripInaccessible/USER_MODE), input validation (binding, String.isId), rate limiting, DTO responses, audit (Event Monitoring).
  • "What's the full_access scope problem?" → Grants org-wide API access to any token using the app — least-privilege scopes per integration are the fix.
  • "Why never serialize raw SObjects?" → Raw SObjects carry every selected field (and FLS gaps); a DTO with exactly the intended fields is the response filter.
  • "Rate limiting inbound?" → Connected App request limits + API governance; Event Monitoring proves usage.

THE REDO

From memory: the five-layer checklist, and the redesign (scopes → context → permission gate → input → DTO).

RETRIEVAL DRILL

  1. The five layers for a secure REST endpoint.
  2. Why is full_access the anti-pattern scope?
  3. The permission gate — which two checks, in order?
  4. Why is JSON.serialize(recs) a leak — and what replaces it?
  5. Input validation for the Id — two tools.

INTERVIEW MAPPING

Agent 05's scenario #5 verbatim ("expose customer data via REST API — what security considerations?") — the integration-security question. The five-layer answer + DTO insight is the complete response.


INCIDENT 8 — THE LEAK NOBODY COULD TRACE

STAKES

Sensitive account data leaked to a competitor. The compliance investigation asks three questions: (1) who changed the deal amount? (2) who changed the permission that allowed it? (3) who SAW the data? The answers: (1) Field History was never enabled on the object — "it costs storage"; (2) the Setup Audit Trail exists but nobody can read it ("it's JSON in a weird object"); (3) there is no read-audit at all — the org has no Event Monitoring. The org cannot answer ANY of the three questions. The regulator's question to the dev team: "What audit surfaces should have existed, and which one is the only one that proves reads?"

THE INCIDENT

Question 1 (who changed the record):  Field History disabled on Deal__c
Question 2 (who changed Setup):       Setup Audit Trail exists but unread; no tracking setup user changes
Question 3 (who SAW the data):        No Event Monitoring license; no API usage logs
Auditor: "You cannot answer three questions. Prove this never happens again."

THE PROBLEM

Name the audit surfaces (one line each, with what each proves): Field History, Field Audit Trail, Setup Audit Trail, Login History, Event Monitoring, Shield/encryption. Then answer the three questions with the right surface, explain why Event Monitoring is the ONLY surface that proves reads, and design the audit-minimum for this org (what you enable, what you schedule, what you retain).

Write: (1) the surface map, (2) the three-question answer key, (3) the audit-minimum design.


HINT LADDER

  • Hint 1 (the avenue): (1) Surfaces: Field History (who changed a record's tracked fields — record-level change log); Field Audit Trail (Setup-managed field-tracking policy); Setup Audit Trail (who changed Setup/security settings — SetupAuditTrail object); Login History (who logged in, when, from where); Event Monitoring (who accessed/queried/exported WHAT — including READS — the only surface that proves "saw"); Shield / Field-level Encryption (data-at-rest protection, not audit). (2) Answers: Q1 → Field History (or Field Audit Trail); Q2 → Setup Audit Trail (+ track Setup user changes); Q3 → Event Monitoring (API/UI access logs). (3) Design: enable Field History on sensitive objects (policy-driven, not all objects); read Setup Audit Trail via SetupAuditTrail/SOQL or the Setup UI; license Event Monitoring for API + UI access on sensitive data; schedule retention + alerting (log reviews); encrypt the crown-jewel fields with Shield.
  • Hint 2 (the mechanism): (1) Field History = per-record, per-field change log (who/when/old/new) — enabled per object with a tracking policy; missing here = Q1 unanswerable. Setup Audit Trail = who changed Setup (permissions, automation, security settings) — stored as SetupAuditTrail records (queryable via SOQL/API, or the Setup UI); must be read to be evidence. Event Monitoring = the usage telemetry (API calls, report exports, UI access, query activity) — the ONLY surface that records READS; without it, "who saw the data" is structurally unanswerable. (2) The three-question key: Q1=Field History; Q2=Setup Audit Trail; Q3=Event Monitoring. (3) Design: object-level Field History policy (sensitive objects only); SetupAuditTrail SOQL review scheduled monthly; Event Monitoring on the org (or at least API + report-export events) with alerting on anomalies (bulk exports, off-hours access); retention per compliance; Shield FLE for at-rest protection of crown-jewel fields (defense-in-depth: encrypted even if the other layers fail).
  • Hint 3 (the skeleton): Map: Field History → record changes; Setup Audit Trail → Setup changes; Login History → authentication; Event Monitoring → usage incl. READS; Shield → at-rest encryption. Answer key: Q1=Field History, Q2=Setup Audit Trail, Q3=Event Monitoring (the only read-proof). Minimum: Field History on sensitive objects; monthly SetupAuditTrail review; Event Monitoring + anomaly alerts (bulk exports, off-hours); Shield on SSN/salary; retention policy per compliance.

THE REVEAL — POSTMORTEM

What actually happened (real class of incidents — the unanswerable audit; agent 05's "how do you audit security / sensitive data?" Q&A):

The surface map (one line each — memorize):

  • Field History — who changed what on a record (per-field old/new values, timestamps, user). Enabled per object with a tracking policy (limit: tracked fields + storage).
  • Field Audit Trail — the Setup-managed policy for field-level tracking (admin-enforced standard).
  • Setup Audit Trail — who changed Setup (permissions, automation, security settings) — stored in the SetupAuditTrail object, queryable via SOQL/API or the Setup UI. Evidence only if read.
  • Login History — who authenticated, when, from where (auth layer, not data layer).
  • Event Monitoring — who accessed what: API calls, report exports, UI access, query activity. The ONLY surface that records READS.
  • Shield / Field-level Encryption — at-rest protection (encrypted values in the DB), not an audit log — the last line of defense, not the evidence.

The three-question answer key (the interview answer): Q1 (who changed the record) → Field History (missing here — "costs storage" was a policy decision that made the question unanswerable). Q2 (who changed the permission) → Setup Audit Trail (existed but unread — evidence that isn't reviewed isn't evidence). Q3 (who SAW the data) → Event Monitoring — the only surface that proves reads; without it, "who saw the data" is structurally unanswerable. One-liner: "Field History proves changes, Setup Audit Trail proves Setup changes, Login History proves authentication, and Event Monitoring alone proves access — because reads leave no trace anywhere else."

The audit-minimum design (the senior fix):

  1. Field History on sensitive objects (tracking policy: the fields that matter — amount, status, owner, confidential flag) — a storage decision, budgeted deliberately, not skipped by default.
  2. Setup Audit Trail reviewed on a schedule (SetupAuditTrail SOQL monthly; permission-change review with Incident 6's access reviews) — evidence must be read to exist.
  3. Event Monitoring (API + report-export + UI access events on sensitive data) with anomaly alerting — bulk exports, off-hours access, failed-login spikes (the 14,000-requests-hour bot from Incidents 3/7 is exactly what this catches).
  4. Shield FLE on crown-jewel fields (SSN, salary) — at-rest encryption as defense-in-depth, so a DB-level leak isn't a data leak.
  5. Retention + compliance mapping — what's kept, for how long, per regulation; alerting on the alerters (the audit of the audit).

Why the "obvious fixes" failed (the contrast):

  • "Enable Field History on everything" → storage + performance cost; a policy (sensitive objects, tracked fields) is the design, not a global switch.
  • "Read the Setup Audit Trail once" → a snapshot is not an audit program; scheduled review + alerting is.
  • "Add Shield encryption" → encryption protects at rest; it doesn't prove who saw what — Event Monitoring is the read-proof; Shield is the last line, not the evidence.

KNOWLEDGE EXTRACTION (interview-ready)

  • "How do you audit security / sensitive data?" → Field History (record changes), Setup Audit Trail (SetupAuditTrail — Setup changes), Login History (auth), Event Monitoring (usage incl. reads — the only read-proof), Shield FLE (at-rest). Plus scheduled reviews, retention, anomaly alerting.
  • "Which surface proves who SAW data?" → Event Monitoring — reads leave no trace anywhere else.
  • "Field History vs Field Audit Trail?" → Field History: per-record change log enabled per object. Field Audit Trail: the Setup-managed tracking policy/standard.
  • "Shield?" → Platform encryption (FLE): encrypted at rest, visible via API with decryption perms — defense-in-depth, not audit.

THE REDO

From memory: the surface map (6 surfaces, one line each), the three-question answer key, and the audit-minimum design (5 points).

RETRIEVAL DRILL

  1. The six audit surfaces — one line each.
  2. Which question does each of Q1/Q2/Q3 map to?
  3. Why is Event Monitoring the ONLY read-proof?
  4. Why isn't Shield an audit surface?
  5. The audit-minimum design — 5 points.

INTERVIEW MAPPING

Agent 05's audit Q&A ("how do you audit security / sensitive data?") — the compliance-flavored question. The "Event Monitoring alone proves reads" insight + scheduled-review discipline is the senior answer.


🏆 CAPSTONE — THE ORG WHERE ONE USER SAW EVERYTHING

STAKES

Thursday, 5:47 PM. Compliance calls: a sales rep's LWC search returned another team's private accounts, a recruiter's component rendered SSN fields, an internal audit found an unsecured REST endpoint, and the org cannot prove who saw what for the last 90 days. The VP wants ONE presentation: "what leaked, why, and how do we guarantee it never happens again?" You have 45 minutes. Every clue maps to an incident in this module.

THE INCIDENT (the evidence file)

  • Clue A: A rep's Account search LWC returns 40 accounts the rep can't access. The class has no sharing keyword. "The org is Private."
  • Clue B: A recruiter's Candidate component renders Salary_Expectation__c and SSN__c — the class SELECTs all fields; the profile denies them; the component renders the payload.
  • Clue C: The partner REST endpoint: full_access scope, without sharing, concatenated Id in dynamic SOQL, JSON.serialize(recs) of raw SObjects. A bot hit it 14,000 times in an hour.
  • Clue D: Deal__c is Private, hierarchy enabled; a director saw every deal including "Confidential" ones (criteria rule grants to "Directors+").
  • Clue E: An "Everyone" permission set group contains the Manage_Compensation__c custom permission; 400 users hold it; nobody knows why.
  • Clue F: The nightly summary batch wrote team revenue into a Public Read object. "It's a batch job, it just runs."
  • Clue G: Compliance asks "who changed the amount, who changed the permission, who SAW the data" — the org can answer none of the three.

THE PROBLEM (the transfer test — the real interview scenario round)

Produce, in writing, a complete incident report:

  1. For each clue: name the mechanism (1 line), the root cause (2–3 lines), and the fix (pointer to the pattern — no full code).
  2. Prioritize: what do you fix tonight vs this week vs this quarter?
  3. Identify the shared root cause that connects at least 4 clues (there is one — find it).
  4. Write the 3 verification steps you'd add before Monday's release.
  5. Role-play the interview: the VP asks "why is the org like this, and how do you guarantee it won't happen again?" — answer in 2 minutes, closed notes.

This is deliberately hard. Produce your best report even if incomplete — the comparison with the model answer below is where the learning lives. (40–45 min cap.)


THE MODEL REPORT (reveal after your attempt)

  1. Clue-by-clue:
    • A → Incident 1: no sharing keyword = system mode → the "Private" org is bypassed in Apex. Fix: inherited sharing + FLS enforcement (stripInaccessible/USER_MODE).
    • B → Incident 2: FLS enforced by UI, not Apex — the class returned denied fields; the component rendered them. Fix: minimal projection + stripInaccessible/USER_MODE + component renders only what was sent.
    • C → Incident 7: full_access scope + without sharing + concatenated Id + raw serialization + no rate limit — the five layers all missing. Fix: least-privilege scope, with sharing, binding + String.isId, DTO response, rate limits, Event Monitoring.
    • D → Incident 5: hierarchy (enabled) + over-broad criteria rule — "Confidential" flag is data, not access. Fix: hierarchy off for confidential data + rules scoped to named groups + explicit grants.
    • E → Incident 6: sensitive custom permission in an "Everyone" group — assignment is the blast radius. Fix: smallest-group assignment + quarterly access reviews + permission-set-assignment reports + logging around FeatureManagement.checkPermission.
    • F → Incident 4: un-keyworded batch wrote to a Public Read destination — the leak is the destination. Fix: destination OWD Private + sharing rule + keyword policy + read-back test.
    • G → Incident 8: no Field History, unread Setup Audit Trail, no Event Monitoring — three unanswerable questions. Fix: Field History policy on sensitive objects, scheduled SetupAuditTrail review, Event Monitoring + anomaly alerts, Shield on crown jewels.
  2. Priorities: Tonight — stop the bleeding: revoke/rotate the partner app's tokens + disable the endpoint (C), restrict the "Everyone" group (E), and pause the leaky search + candidate components (A, B) or deactivate the classes. This week — fix the classes (keywords + FLS + DTO), the destination OWD + rules (F), the hierarchy/rules for confidential data (D), and enable Field History + Event Monitoring (G). This quarter — the security program: the five-layer endpoint standard, the access-review cadence, the audit-minimum, and security regression checks in every release.
  3. The shared root cause (find it): "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. Say this first — it's also the interview answer to "why is the org like this?"
  4. The 3 verification steps: (a) Context regression — a sandbox user cloned from a rep profile runs every new Apex entry point (search, components, endpoint) and asserts the record/field results are exactly what the profile allows (Incident 1's Debug-as-user as a test); (b) Endpoint penetration smoke — a test calling the REST endpoint with a limited-scope token + injection payloads asserts: denied fields absent, injection returns no rows, rate limit trips (Incidents 3 + 7 as tests); (c) Audit proof — after the fixes, a rep-context query on the summary + Field History/Event Monitoring logs exist and are queryable (Incidents 4 + 8 as tests).
  5. 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."

THE KNOWLEDGE SPINE (the allowed 5% — memorize after the incidents)

The sharing model chain (reproduce cold)

OWD (baseline: Private / Public Read Only / Public Read-Write / Controlled by Parent) → role hierarchy (managers above the owner inherit, when "Grant Access Using Hierarchies" is enabled; bypassed by without sharing / View All Data / system context) → sharing rules (declarative, criteria/owner-based, group grants — can only ADD) → manual/Apex sharing (per-record __Share grants, managed sharing in Apex). Cumulative: each link adds access above the baseline.

The three keywords (reproduce cold)

KeywordBehaviorUse
with sharingEnforces running user's record accessUser-facing classes
without sharingBypasses sharingPrivileged jobs (justify + comment)
inherited sharing (API 45+)Inherits caller's modeThe recommended default
(no keyword)SYSTEM MODE — the trapNever by accident

The FLS enforcement hierarchy (2026 tools)

  1. Schema.SObjectType.X.fields.F.isAccessible() — granular describe check.
  2. Security.stripInaccessible(AccessType.READ, records).getRecords() — API 50+, bulk-safe, strips denied fields.
  3. ... WITH USER_MODE — API 59+, enforcement at query time (vs WITH SYSTEM_MODE = explicit bypass). Rule: with sharing = record access; FLS = separate filter — always both.

Injection defense (reproduce cold)

  1. Bind variables for values (the primary defense — kills the injection class).
  2. String.escapeSingleQuotes() for unavoidable dynamic strings (a patch, not a design).
  3. Whitelist/describe-validate every dynamic object/field name (names can't be bound). One-liner: user input is never query syntax and never a name.

Access entities (reproduce cold)

  • Profiles = baseline licenses + baseline access (lean by design)
  • Permission sets = granular additions, no profile change (the modern practice)
  • Permission set groups = role-based bundles
  • Custom permissions = code-checked capabilities: FeatureManagement.checkPermission('Name') Practice: lean profile + permission sets + groups; smallest-group assignment; quarterly reviews.

Audit surfaces (reproduce cold)

  • Field History → who changed record fields (per-object policy)
  • Setup Audit Trail (SetupAuditTrail) → who changed Setup (read it to be evidence)
  • Login History → who authenticated
  • Event Monitoring → who ACCESSED/SAW data — the only read-proof
  • Shield FLE → at-rest encryption (defense-in-depth, not audit)

Endpoint security — the five layers

  1. Auth & scopes (least privilege; never full_access)
  2. CRUD/FLS + sharing (keyword + stripInaccessible/USER_MODE)
  3. Input validation (binding, String.isId, allowlists)
  4. Rate limiting
  5. Audit (Event Monitoring) Plus: DTO responses — never JSON.serialize raw SObjects.

Quick one-liners

  • "Apex defaults to system mode — the keyword is the fix."
  • "with sharing fixes records; FLS is a separate filter."
  • "Criteria rules can only grant; the hierarchy never reads your flags."
  • "The leak is often the destination, not the source."
  • "A permission is a key, not a badge — smallest set, smallest people, reviewed quarterly."
  • "Field History proves changes; Event Monitoring alone proves reads."
  • "Security by assumption fails; security by declaration works."

Rapid-fire trick questions (module 5 scope)

QuestionAnswer
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 sharing enforce 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 (do 1–2 per session, closed-book)

Pick the technique before solving — the choice is the training. Mixes Modules 1–5.

  1. Limit hunt (which wallet, which module): (a) a no-keyword class called by an LWC in a Private org; (b) a class SELECTing SSN__c with with sharing; (c) Database.query('... WHERE Name = \'' + input + '\''); (d) a batch writing aggregates to a Public Read object; (e) a without sharing class called from a trigger.
  2. Design (2 minutes, closed notes): "partner app needs read-only access to 5 fields of the Account it was shared." Endpoint or Connected App flow? Which five layers? Keyword? Scope? Response shape? How do you prove who accessed it?
  3. Module-1 bridge: a batch class with no keyword fails its read-back test (rep can't see the summary). Name the fix in two parts (Module 1's batch lesson × Incident 4's destination lesson) — and the keyword that would have silently under-reported.
  4. Module-4 bridge: a record-triggered Flow writes sensitive aggregates to a Public Read object. Which security lessons from this module apply to flows, and what does "flows run in system context" mean for the fix?
  5. The one-card answer: write the complete "user can't see data returned by your Apex class — why?" answer in 5 bullet lines — then say which incidents each bullet maps to.

SPACED REPETITION SCHEDULE (log it in the canvas)

  • Today: after each incident — retrieval drill + redo.
  • Tomorrow: re-answer the 5-question drills from Incidents 1–4 (closed-book).
  • +1 week: the Interleaved Practice Set + rapid-fire bank (all five modules).
  • +1 month: the Capstone (re-do from memory) + Modules 1–4 capstones back to back.

Incident sources (real, for your curiosity): agent 05 research report (security question bank: sharing model, with/without/inherited sharing, CRUD/FLS, stripInaccessible, WITH USER_MODE, profiles vs permission sets vs custom permissions vs groups, REST endpoint security, SOQL injection, role hierarchy, audit surfaces; scenarios #2 and #5; traps #3–5); trailhead.salesforce.com (Sharing, CRUD/FLS, Apex security modules); developer.salesforce.com/docs (Apex security, Security.stripInaccessible, SOQL injection, Apex REST, Event Monitoring); thesalesforcemonk.com (security interview Q&A); salesforceben.com (sharing model explainer). Full URL list in _research/round1_master_report/agent_05_flows_security/sources.md + links_master.md.

On this page

M0 — THE MAP (read this first, 5–10 min)The one idea everything hangs on: SECURITY IS A LAYERED FILTER, AND APEX'S DEFAULT IS "NO FILTER"The incidents (choose your own adventure — recommended order)INCIDENT 1 — THE SEARCH THAT RETURNED EVERYTHINGSTAKESTHE INCIDENTTHE PROBLEMTHE REVEAL — POSTMORTEMKNOWLEDGE EXTRACTION (interview-ready)THE REDORETRIEVAL DRILLINTERVIEW MAPPINGINCIDENT 2 — THE FIELD THE REP SHOULDN'T HAVE SEENSTAKESTHE INCIDENTTHE PROBLEMTHE REVEAL — POSTMORTEMKNOWLEDGE EXTRACTION (interview-ready)THE REDORETRIEVAL DRILLINTERVIEW MAPPINGINCIDENT 3 — THE SEARCH BOX THAT RAN YOUR SOQLSTAKESTHE INCIDENTTHE PROBLEMTHE REVEAL — POSTMORTEMKNOWLEDGE EXTRACTION (interview-ready)THE REDORETRIEVAL DRILLINTERVIEW MAPPINGINCIDENT 4 — THE NIGHTLY JOB THAT LEAKED PRIVATE DATASTAKESTHE INCIDENTTHE PROBLEMTHE REVEAL — POSTMORTEMKNOWLEDGE EXTRACTION (interview-ready)THE REDORETRIEVAL DRILLINTERVIEW MAPPINGINCIDENT 5 — THE HIERARCHY THAT OPENED EVERY DOORSTAKESTHE INCIDENTTHE PROBLEMTHE REVEAL — POSTMORTEMKNOWLEDGE EXTRACTION (interview-ready)THE REDORETRIEVAL DRILLINTERVIEW MAPPINGINCIDENT 6 — THE PERMISSION SET THAT BECAME A CROWNSTAKESTHE INCIDENTTHE PROBLEMTHE REVEAL — POSTMORTEMKNOWLEDGE EXTRACTION (interview-ready)THE REDORETRIEVAL DRILLINTERVIEW MAPPINGINCIDENT 7 — THE ENDPOINT THAT SERVED EVERYONE'S DATASTAKESTHE INCIDENTTHE PROBLEMTHE REVEAL — POSTMORTEMKNOWLEDGE EXTRACTION (interview-ready)THE REDORETRIEVAL DRILLINTERVIEW MAPPINGINCIDENT 8 — THE LEAK NOBODY COULD TRACESTAKESTHE INCIDENTTHE PROBLEMTHE REVEAL — POSTMORTEMKNOWLEDGE EXTRACTION (interview-ready)THE REDORETRIEVAL DRILLINTERVIEW MAPPING🏆 CAPSTONE — THE ORG WHERE ONE USER SAW EVERYTHINGSTAKESTHE INCIDENT (the evidence file)THE PROBLEM (the transfer test — the real interview scenario round)THE MODEL REPORT (reveal after your attempt)THE KNOWLEDGE SPINE (the allowed 5% — memorize after the incidents)The sharing model chain (reproduce cold)The three keywords (reproduce cold)The FLS enforcement hierarchy (2026 tools)Injection defense (reproduce cold)Access entities (reproduce cold)Audit surfaces (reproduce cold)Endpoint security — the five layersQuick one-linersRapid-fire trick questions (module 5 scope)INTERLEAVED PRACTICE SET (do 1–2 per session, closed-book)SPACED REPETITION SCHEDULE (log it in the canvas)