For Salesforce architects, admins and security reviewers who are asked for a threat model and have only ever produced a findings list. The first half works without any tool. The second half is how sf-audit changed when I made it think this way.
TL;DR
A security audit tells you which settings are wrong. A threat model tells you who could use them, for what, and which fix stops them. The frameworks are not interchangeable. Each one belongs at its own layer: STRIDE on each control, LINDDUN where personal data is involved, MITRE ATT&CK on what an attacker gains, and abuse cases on whole attack chains. sf-audit added all four between v1.20 and v1.22. Writing the model down exposed claims the tool could not back up, and v1.21 to v1.24 corrected them.
What You'll Learn
- How the four standard threat-modelling questions map onto a Salesforce org
- Which framework to use for which question, and why mixing them up produces a bad model
- Five rules that stop a threat model from reading as more assurance than it is
- How sf-audit went from a checklist to attack chains to a threat model, release by release
- What the threat model found wrong in sf-audit itself
The Problem
Most Salesforce security reviews produce a list. MFA is not enforced for these users. This guest profile can read Contacts. This Apex class runs without sharing. Each item has a severity, and the list is sorted by it.
Then an architect, an assessor or a privacy officer asks for the threat model, and the list does not answer it. They want to know who the attacker is, what they are after, how they would get there, and which change stops them. A security architect asks in STRIDE. A privacy officer asks in LINDDUN. The SOC asks in MITRE ATT&CK. A vendor assessment asks for abuse cases. So someone re-maps the findings by hand for each reader, and does it slightly differently every time.
A list sorted by severity also hides how findings combine. Three medium findings that add up to an unauthenticated route to bulk data matter more than a lone high that leads nowhere. I wrote about that in attack chain detection. A threat model is the step after chains: it gives each chain an attacker, a goal and a fix that provably stops it, in vocabulary each reader already uses.
Common questions this article answers:
- How do I threat model my Salesforce org when it is a SaaS platform I do not run?
- Should I use STRIDE, LINDDUN or MITRE ATT&CK for my org?
- If a threat category has no findings, does that mean my org is safe from it?
Quick Answer
Use the four threat-modelling questions, with Salesforce surfaces as the system:
- What are we working on? The ways into your org: Experience Cloud sites and their guest user, portal and community users, integrations and connected apps, Agentforce agents, inbound email services, internal users, and sandboxes holding production data.
- What can go wrong? Tag each finding with STRIDE, and with LINDDUN where personal data is involved. Then join findings into attack chains and write each chain as an abuse case: an actor, a goal, and the steps.
- What are we going to do about it? For each chain, name the break point: the one fix whose removal stops the chain every time it fires. Fix break points first.
- Did we do a good job? Report coverage honestly. A category with no findings from checks that ran is different from one nothing checked.
If you would rather not do this by hand, sf-audit v1.22 and later does it on every run:
sf plugins install @cclabsnz/sf-audit
sf audit security --target-org my-org --format md,executive
Threat Modelling When You Do Not Own the Platform
Threat modelling usually starts from a design diagram. With Salesforce you did not design the platform, and Salesforce secures the platform itself. What you own is configuration: who can reach the org, what each of them can see and do, and what would notice if they did it.
That makes the first question concrete. List every principal that can reach the org and how:
| Principal | Way in |
|---|---|
| Unauthenticated visitor | An Experience Cloud site's guest user, an inbound email service address, an Agentforce chat channel |
| Low-trust authenticated user | Portal and community users, self-registration, social sign-on |
| Integration | Connected apps and their OAuth tokens, named credentials, API-only users |
| Agent | An Agentforce agent's run-as user and its actions |
| Internal user | Profiles, permission sets, delegated admin |
| Anyone in a sandbox | A copy of production data under the sandbox's controls |
Every finding in a Salesforce audit is about one of these principals having more reach than intended. The threat model is the work of saying which principal, what they gain, and what stops them.
Four Frameworks, Four Layers
The frameworks answer different questions, so each attaches at a different layer. Getting this wrong is the most common mistake: STRIDE tags on an attack chain, or ATT&CK techniques on a configuration check, read as noise.
| Framework | Kind | Attach it to | Question it answers |
|---|---|---|---|
| STRIDE | Security threat category | Each control or check | What kind of harm does this weakness allow? |
| LINDDUN | Privacy threat category | Controls that touch personal data | What privacy harm does this allow? |
| MITRE ATT&CK | Adversary technique | What an attacker gains from a finding | What would an attacker do with this? |
| Abuse case | Misuse story | A whole attack chain | Who, wanting what, gets there how? |
| OWASP and other control catalogues | Compliance control | Each check | Is this requirement met? |
STRIDE on each control
STRIDE (Spoofing, Tampering, Repudiation, Information disclosure, Denial of service, Elevation of privilege) sorts weaknesses by the kind of harm they allow. In a Salesforce org the mapping is mostly natural:
| STRIDE | Salesforce examples |
|---|---|
| Spoofing | MFA enforcement, SSO enforcement, login and session settings, auth providers |
| Tampering | Who can deploy, installed packages, guest create permission |
| Repudiation | Setup Audit Trail, field history tracking, Event Monitoring, SIEM forwarding |
| Information disclosure | Sharing model, field-level security, content links, hardcoded credentials, guest read access |
| Denial of service | API limits, certificate expiry, guest API hard delete |
| Elevation of privilege | Permissions, admin assignments, Apex sharing, flows running without sharing, Apex CRUD and FLS |
Some checks need a different category per finding. A guest that can read records is information disclosure. The same check finding that a guest can create records is tampering. When findings from one check differ like this, tag each finding.
LINDDUN where personal data is involved
LINDDUN does for privacy what STRIDE does for security: Linking, Identifying, Non-repudiation, Detecting, Data disclosure, Unawareness and unintervenability, Non-compliance. Only tag the controls that concern who can see, link, identify or keep personal data: guest and external exposure, sharing, field-level security, data classification, data export, field history, log retention, and Apex that logs sensitive values. A control with no privacy bearing gets no LINDDUN tag. Do not give everything a default.
LINDDUN earns its place on logging. STRIDE says a missing audit trail is a repudiation gap: nobody can prove who did what. LINDDUN says the logging you add to close it records personal data, so you now have to decide what it captures, how long you keep it, and who can read it. Both are true. A good threat model shows both readings and lets neither cancel the other.
MITRE ATT&CK on what an attacker gains
ATT&CK describes what attackers do, so attach it to what a finding gives an attacker. A guest that can read Account records gives an attacker data access, which ATT&CK files as T1213.004, Data from Information Repositories: Customer Relationship Management Software. ATT&CK names Salesforce as an example of that technique. Where a capability's usual technique does not describe a particular finding, give that finding its own technique, or none.
Two cautions. First, check every technique against the ATT&CK release you cite. Techniques get revoked and renumbered between releases. Second, judge the fit, not just the id. A misconfigured guest profile is not obviously "Exploit Public-Facing Application" until you read that ATT&CK counts a misconfigured Internet-facing system as the weakness exploited.
Detection gaps get a separate treatment. A finding that nothing alerts on suspicious logins does not grant a technique. It leaves techniques unobserved, such as brute force and the use of a stolen account. Keep those two lists apart.
Abuse cases on attack chains
An attack chain is a set of findings that together make a route from a way in to a harm. Written as an abuse case, it has four parts:
- Actor: a role, never a person. "An unauthenticated site visitor."
- Goal: the harm, in the attacker's terms. "Read or change business data in bulk without logging in."
- Preconditions: the findings that must all be present.
- Break point: the one group of findings whose complete removal stops the chain.
The break point is the part that changes what you fix first. For an org where a low-trust user can escalate to full control, the break point is "remove every escalation permission held by someone who is not an administrator". Closing the way in is useful too, but there are usually several ways in, and only the break point stops the chain whichever one is used.
Here is a real one, from an sf-audit run against my own Developer Edition org. The abuse case reads: "an internal user or whoever holds their stolen credentials wants to export business data in bulk without a record of it." Eight findings make up the chain. Two of them are the break point: a permission set granting Weekly Data Export, and permission sets that combine API access with View All Data or Modify All Data. Fixing both stops the chain, whatever happens to the other six. Four of the steps carry an ATT&CK technique, and it is the same one each time: T1213.004.

Five Rules for an Honest Threat Model
A threat model can claim more assurance than a findings list, which makes it more dangerous when it is wrong. These are the rules I hold sf-audit to. They apply just as well to a model in a spreadsheet.
- An empty category is not a pass. "No findings from 12 checks", "not assessed in this run" and "not assessed by this tool" are three different statements. Never write "no threats" or "compliant" for a threat category. Say how many checks looked: on my Developer Edition org, the repudiation row reads "5 findings (from 12 checks)".
- A threat category is not a compliance control. A control asks whether a requirement is met. A category asks what kind of harm is possible. Keep them in separate tables.
- Threat tags carry no weight. Adding STRIDE or ATT&CK labels must not change a finding's severity or the org's grade. The model describes the risk, and scoring stays where it was.
- A break point must break the chain. Before you name a fix as the one that stops a chain, take it out and check the chain no longer holds. If it still holds, it is a recommended fix, not a break point.
- Show privacy and security both ways. When STRIDE and LINDDUN disagree about a setting, show both readings. Do not net one against the other.
How sf-audit Got Here
sf-audit is a read-only Salesforce CLI plugin I started in March 2026. It did not start as a threat model. It got there in four stages.
A checklist. The first releases ran checks and graded the org A to F, like most audit tools. Each finding stood alone, sorted by severity. The v1.0 write-up is from that stage.
Attack chains. In June the tool started correlating findings into named attack chains: combinations such as a guest foothold plus guest-callable Apex running without sharing. Each finding declares the capabilities it grants an attacker (an unauthenticated foothold, bulk data read, privilege escalation, external egress), and a chain fires when its capabilities join up. In September I found the chain model could not see half its own checks, because they granted no capability. Fixing that unlocked five new chains.
A threat model (v1.20 to v1.22, 28 and 29 October). This is the release set this post is about:
- v1.20.0 tagged all 109 checks with STRIDE and the 32 that touch personal data with LINDDUN. A threat view in the Markdown report shows each category as "N findings (from N checks)", "no findings from N checks", "not assessed in this run" or "not assessed by this tool". Every one of the 19 named chains became an abuse case with an actor, goal, preconditions and a proven break point.
- v1.21.0 added MITRE ATT&CK techniques on the capabilities a finding grants, pinned to Enterprise ATT&CK v20.0. Detection gaps say which techniques they leave unobserved. Chain steps now run in ATT&CK tactic order, from initial access to the goal.
- v1.22.0 turned the executive report into a deck with the threat model on its own slides: findings per ATT&CK tactic, the tactics each chain reaches, and a STRIDE and LINDDUN heatmap. Its action plan is computed from the break points, and actions are ranked by the risk they remove, with a broken chain counting double.
These are the two threat-model slides from the same Developer Edition run:


A finding counts in every category it touches, so the STRIDE totals add up to more than the number of findings.
Correcting the tool (v1.23 to v1.24.1, 29 October to 1 November). This part I did not plan for.
What the Threat Model Found Wrong in sf-audit
Writing the model down forced every claim to be stated precisely enough to check, and some did not survive.
Break points had to be proven. Each named chain now has a test that fires it on a full fixture, removes its break point, and checks it no longer fires. The same work found that one chain, credential theft to external pivot, fired on orgs with no exposed secret at all. It now needs one.
ATT&CK fits had to be defended. An adversarial review of the first technique mapping found fifteen findings carrying a technique that did not describe them. A guest hard delete is data destruction, not data manipulation. Self-registration is creating an account. A list of guest users is account discovery. Active debug traces turned out to grant no attacker capability, and wildcard CORS origins stopped counting as an exposed secret, so neither feeds the credential-theft chain any more. One technique in the draft mapping had been revoked in the current ATT&CK release.
Tactic order checked the abuse cases. Ordering steps by ATT&CK tactic exposed two chains that did not read as their own story. The active guest reconnaissance chain never reached collection, and the self-registration chain's way in sorted after execution.
The deck was reviewed for platform accuracy. A review of the new executive deck raised six content problems, and an adversarial platform review corrected the first fixes. v1.23 shipped the result. The unauthenticated bulk-exfiltration chain was counting the external org-wide default as a guest step, but secure guest record access has kept guests on Private since Winter '21. The credential-theft chain dropped from critical to high: its secret is a pattern match, and most of its egress steps are the org's own registered endpoints.
Then v1.24 found checks that matched nothing. Salesforce accepts a SOQL filter on a picklist value that does not exist and returns no rows. Three checks filtered on values the platform never writes, so they passed in every org. Two more read guest permissions through a field that holds permission set Ids, not profile Ids, so guest-executable Apex and guest object access were never found. The schema guard now keeps picklist values and fails a query that filters on one the picklist does not have.
Writing this post found two more. The first screenshots for this article counted informational results in the threat view, so a row could look worse than its open findings. And the MFA bypass chain fired because a portal user had no MFA, which is not a path to a privileged account. v1.24.1 leaves informational results out of the threat view and the heatmap, gives weak MFA its own capability (a valid account, T1078.004) so the chain starts at initial access, and fires the chain only when the weakness reaches a privileged user.
The threat model did not cause all of these bugs. It made them visible. A findings list can be quietly wrong for months, because nothing links one finding to another. Once each chain had to name an attacker, a goal and a fix that provably stops it, the gaps were hard to miss.
Frequently Asked Questions
Q: Do I need a tool to threat model a Salesforce org?
A: No. The four questions, the principal table and the framework layers in this post work in a spreadsheet. A tool helps with the repetitive part: tagging every finding, joining findings into chains, and proving break points against your actual configuration on each run.
Q: Should I use STRIDE or MITRE ATT&CK?
A: Both, at different layers. STRIDE describes the kind of harm a weak control allows, so it goes on controls. ATT&CK describes what an attacker does, so it goes on what a finding gives an attacker. Security architects tend to review in STRIDE and SOC teams in ATT&CK, so a model with both answers both audiences.
Q: If the threat view shows no findings for a category, is my org safe from it?
A: No. "No findings from N checks" means those checks ran and found nothing. It says nothing about threats those checks do not cover, which is why sf-audit also reports "not assessed by this tool" for categories it has no checks for. A clean row tells you about coverage only.
Q: Does adding threat tags change sf-audit's grade?
A: No. STRIDE, LINDDUN, ATT&CK and abuse cases are ignored by scoring, grading, --fail-on and the SARIF severity. The same audit with and without them gives the same score, grade and exit code. Grades did change in v1.23 and v1.24, but from the check and chain corrections, not the tags.
Q: Does sf-audit change anything in my org?
A: No. It reads configuration, metadata, selected records and event logs. It is not a penetration test or a code review, so confirm each finding in the org before acting on it.
Key Takeaways
- A findings list answers "what is wrong". A threat model answers "who could use it, for what, and what stops them".
- Put each framework at its own layer: STRIDE on controls, LINDDUN on personal data, ATT&CK on attacker capabilities, abuse cases on chains.
- An empty threat category is a statement about coverage, never assurance.
- Fix break points first. A break point is only a break point if removing it stops the chain.
- Writing a threat model down makes every claim checkable. In sf-audit that exposed overstated chains, misfitting techniques and checks that could never fire.
What's Next?
- Read how findings combine in attack chain correlation and the capability blind spot
- See what the 1.17 checks cover in sf-audit 1.17: inbound email, Platform Events, Data Cloud and package installs
- Install or update with
sf plugins install @cclabsnz/sf-audit, and runsf audit security --target-org my-org --format md,executiveto see the threat view and deck for your own org
Resources & References
- sf-audit on GitHub, with the v1.20.0, v1.21.0, v1.22.0 v1.24.0 and v1.24.1 release notes
- sf-audit attack chains and the ATT&CK verification worksheet
- Threat Modeling Manifesto, the source of the four questions
- Microsoft: STRIDE threat categories
- LINDDUN privacy threat modelling
- MITRE ATT&CK Enterprise matrix and T1213.004 Customer Relationship Management Software
- Salesforce: Secure Guest User Record Access
Responses
Checking your session.
Loading responses.