Threat Modeling: The Process
System → assets → actors → entry points → trust boundaries → threats → mitigations: a repeatable hour of structured thinking that finds design flaws no scanner will ever find, because they are not bugs.
Frame the problem
Security starts with a concrete asset, attacker capability and trust crossing.
Seven steps, in order
Threat modeling has a reputation for being a heavyweight ceremony producing a document nobody reads. It does not have to be: the useful version is a whiteboard, four people who know the system, and about an hour, repeated when the design changes materially. The value is in the order of the questions, because each step constrains the next.
Start with the system: draw the components and the data flows, not the deployment topology. Then assets — what is worth protecting, in specific nouns. Then actors — who interacts with it, including the ones you do not want. Then entry points — where can each actor send input. Then trust boundaries — where does data cross from one actor's control into yours. Only then threats, and only then mitigations.
Doing it in this order matters because jumping to threats produces a list of vulnerabilities from memory (the last thing you read about, the thing that bit you before) rather than a list of threats to *this* system. Doing mitigations before threats produces controls with no stated purpose, which are the first things removed when they become inconvenient.
Making it produce findings rather than paperwork
Three habits separate a threat model that finds real problems from one that produces a compliance artefact.
Name actors adversarially. "User" is not an actor; "a paying customer who wants to read another customer's invoices" is. "Partner API" is not an actor; "a partner whose API key has been stolen" is. The moment actors have goals, the entry points stop being a list of endpoints and become a list of opportunities.
Walk data, not components. Pick a concrete piece of sensitive data — a payment reference, a private message, a session token — and trace every place it exists: the request, the application memory, the database, the cache, the queue, the log line, the analytics warehouse, the backup, the support tool, the developer laptop that ran a query. The copies you find at the end of that walk are almost always the finding.
End every threat with a decision, not an observation. Each threat gets one of four outcomes: mitigate (with a specific control and an owner), accept (with a reason and an expiry), transfer (to a provider or an insurer, honestly described), or eliminate (stop collecting the data, remove the feature). A threat with no decision is a note, and notes do not change systems.
- Actors have goals, not job titles: "an ex-employee whose SSO account was disabled but whose API token was not".
- Follow one sensitive value through every copy: request → app → DB → cache → queue → logs → warehouse → backup → support UI.
- Every threat ends in mitigate / accept / transfer / eliminate, with an owner.
- Timebox to an hour and repeat on material change; a model that takes a week will be done once.
- Write the model where the code lives, so a design change makes it visibly stale.
When to do it, and how deeply
Not every change needs a threat model, and demanding one for everything is the fastest way to get none. The trigger conditions are structural: a new trust boundary (a new integration, a new client type, a new tenant model), a new asset class (you now store health data, or card details, or credentials for someone else's system), a change in who can reach something (an internal service becomes public, a feature moves from admin to self-serve), or a new actor (a partner, a marketplace, an AI agent that can call your tools).
Depth should match the asset. A new internal dashboard reading non-sensitive metrics deserves fifteen minutes and a sanity check on authorization. A payments flow deserves a full model with a written residual-risk list. An AI agent with write-capable tools deserves the full model *plus* the agent-specific boundaries in Agent Trust Boundaries, because the actor list now includes "a document the agent read".
The output that survives is small: a diagram with boundaries marked, a table of threats with decisions, and a residual-risk list. If it is longer than two pages, it will not be updated, and a threat model nobody updates describes a system that no longer exists.
Key points
- Order matters: system → assets → actors → entry points → boundaries → threats → mitigations. Jumping to threats produces a list from memory.
- Name actors with goals, not job titles — that is what turns endpoints into opportunities.
- Walk one sensitive value through every copy; the copies are usually where the finding is.
- Every threat ends in mitigate, accept, transfer or eliminate, with an owner. Notes do not change systems.
- Trigger on new boundaries, new asset classes, new exposure or new actors — and keep the artefact under two pages.
Threat Model Builder
Change the system and observe which assumption moves.
- • Browser → API: authenticate the caller, validate the data and authorize the operation.
- • Backend → Database: authenticate the caller, validate the data and authorize the operation.
- Unmarked: User → Browser, API → Backend. If the other side were hostile, would anything here need checking?
- • Identity question: which component authenticates human and machine principals?
The builder suggests questions, not certainty. Verify each control in code, policy and production configuration; record residual risks and unknowns.
Follow the attack
Safe conceptual simulation: capability → missing control → crossed boundary → asset impact.
- 1Attacker → the component nobody modelled: the internal admin tool, the export job, the partner integration added last quarter.
- 2Unmodelled component → an assumption: it trusts something because everything around it did.
- 3Assumption → asset: the data reached through a path the design never considered, which is why no control was placed there.
- Design flaws found after launch cost orders of magnitude more to fix than the same flaw found on a whiteboard.
- An unmodelled path has no control, no test and no alert, so exploitation is both easy and quiet.
- Missing threat models compound: each unmodelled integration widens the surface for every future one.
Defend, detect, recover
One prevention is a single point of security failure. Layer it and make failure observable.
- • Make a threat model a required artefact for changes that add a boundary, an asset class, an exposure or an actor.
- • Keep the model in the repository next to the code so it goes stale visibly.
- • Use a standard checklist of actor types so the adversarial ones are not forgotten under time pressure.
- • Convert each mitigation into a specific, testable control with an owner rather than a principle.
- • Compare the modelled data flows against actual traffic and actual database grants; divergence is where the model has aged.
- • Track findings that arrive from incidents or researchers and ask whether the model should have caught them.
- • Review models on a cadence tied to deploys touching the boundary, not to the calendar.
- • After an incident, re-run the model for the affected area and add the actor or path that was missing.
- • Add the class of flaw to the standard checklist so it is asked about everywhere, not just where it bit.
- • A model captures the system as understood on one day by the people in the room; both drift.
- • Threats nobody has conceived of are not in the model by definition — which is why detection and response exist.
- • Models are biased toward the components the participants own, and integration points fall between owners.