Every other security topic is a defence. This one is how you work out which defences you actually need.
Without it, security becomes shopping: a team adds a firewall, a scanner and a password policy because those are the things one adds, then ships a feature where any logged-in user can read any other user's invoices by changing a number. The controls were real. They were answering questions nobody had asked.
Threat modelling is the opposite move — decide what you are protecting and from whom, then choose defences that follow.
Four questions, in order
The whole practice fits in four questions, and the order matters.
Diagram, as text
- What are we building? leads to What can go wrong?
- What can go wrong? leads to What are we doing about it?
- What are we doing about it? leads to Did we do a good enough job?
- Did we do a good enough job? leads to What are we building?
- What are we building? A diagram of the system, its data, and — crucially — where trust changes.
- What can go wrong? The threats, enumerated rather than imagined.
- What are we doing about it? A decision per threat: mitigate, accept, transfer, or eliminate.
- Did we do a good enough job? A review, and a return to the first question when the design changes.
Most teams skip straight to the third question. That is what produces the shopping list.
Draw the trust boundaries, not the architecture
The diagram you need is not the one on the architecture wiki. It is a data flow diagram with one addition: a line wherever data crosses from one level of trust to another.
A trust boundary sits between the browser and your API, between your API and a third-party service, between one team's service and another's, between a queue consumer and the queue. Every boundary is a place where an assumption is being made about the other side, and every one of those assumptions is a candidate threat.
STRIDE: enumeration beats imagination
Left to free association, everyone imagines the same two or three attacks. A checklist forces the categories you would not have thought of.
STRIDE gives six, each the violation of one property:
- Spoofing — pretending to be someone else. Violates authentication.
- Tampering — modifying data in transit or at rest. Violates integrity.
- Repudiation — denying an action with no way to prove otherwise. Violates non-repudiation.
- Information disclosure — exposing data to those who should not see it. Violates confidentiality.
- Denial of service — making the system unavailable. Violates availability.
- Elevation of privilege — gaining capabilities you should not have. Violates authorisation.
Walk each boundary in your diagram against all six. Most combinations are irrelevant in seconds; the value is in the two or three that are not, which nobody would have volunteered.
Rank by consequence, not by cleverness
Not every threat deserves work. Rank by two axes:
- How bad is it if this happens? Data exposed, money lost, service down, trust destroyed.
- How likely is it? Which really asks how much skill, access and motivation it needs.
An attack requiring a nation-state budget to reveal a public blog post is not worth defending. An attack requiring a changed number in a URL to reveal another customer's invoice is worth stopping today. The cheap-to-exploit, high-consequence quadrant is where nearly all real breaches live, and it is rarely where the interesting cryptography is.
Four responses, and three are not "fix it"
For each threat that matters, choose deliberately:
- Mitigate — reduce likelihood or impact. The default, and the only one people usually consider.
- Eliminate — remove the feature or the data. Astonishingly effective and constantly overlooked: data you never collect cannot be breached.
- Transfer — make it someone else's problem, contractually or technically. Using a payment provider transfers most card-data risk.
- Accept — decide the cost of defending exceeds the cost of the risk, write it down, and move on.
Do it when it is still cheap
A threat model is worth most before the code exists, when changing the design costs a conversation. The same finding after launch costs a migration.
That does not mean a formal exercise every sprint. In practice it works best as:
- A short session per significant feature, at design time, with the people who will build it.
- A trigger list: any new trust boundary, any new class of data, any new third party, any change to authentication or authorisation.
- A living document rather than an artefact produced once for an audit and never opened again.
Check your understanding
Common mistakes
Starting with controls instead of assets. A list of defences with no statement of what is being protected cannot be checked for completeness or relevance.
Modelling the architecture rather than the data flows. Boxes and arrows between services do not show where trust changes, which is the only thing the exercise needs.
Treating it as a security-team activity. The people who know what can go wrong are the ones building it. A model produced by a separate team is a review, not a threat model.
Producing a document and stopping. The output that matters is the decisions and the changes to the design, not the artefact.
Doing it once. A trust boundary added six months later is unmodelled, and it is exactly where the next incident will be.
Practice
Threat model a feature that lets users upload a profile photo and displays it publicly. Work through the four questions and produce at least four distinct threats with responses.
Write your own answer first, on paper or in a note. Then compare.
Show a hint
Follow the file: uploaded by whom, processed by what, stored where, served to whom.
Show a strong answer
What are we building? A browser uploads a file to an API, which stores it in object storage and serves it publicly through a CDN. Trust boundaries: browser → API, API → image processor, storage → CDN → public internet.
What can go wrong — walking the flow with STRIDE:
- Information disclosure via metadata. Photos carry EXIF data including GPS coordinates. Publishing a user's home location because they uploaded a selfie is a serious harm they never consented to. Response: eliminate — strip all metadata on upload. Cheap, complete, no downside.
- Elevation of privilege via the processor. Image libraries parse untrusted binary formats and have a long history of memory-corruption bugs. A malicious file can become code execution on your server. Response: mitigate — process in an isolated sandbox with no network access and no credentials, and treat the processor as compromisable.
- Tampering / content abuse. The upload is a public publishing channel: illegal or abusive imagery served from your domain. Response: mitigate — automated scanning plus a reporting path and takedown process. Also transfer in part, by using a moderation provider.
- Denial of service via decompression. A small file that expands to gigabytes exhausts memory. Response: mitigate — limit dimensions and decoded size, not just the uploaded byte count, which is the check people write.
- Spoofing via content type. A file named
.jpgcontaining HTML, served from your domain, executes as a page in your origin. Response: mitigate — serve user content from a separate domain, set a strict content type, and force download rather than inline rendering where appropriate.
Did we do a good enough job? The residual risks are moderation latency and unknown parser bugs, both accepted and recorded, with the sandbox limiting the blast radius of the second.
Your team ships weekly and cannot threat model every change. Design a trigger list that catches what matters without becoming a tax on every ticket.
Write your own answer first, on paper or in a note. Then compare.
Show a hint
Most changes introduce no new threats. Identify what actually does.
Show a strong answer
The goal is a small, unambiguous list, because a trigger that requires judgement will be argued about and then ignored.
Triggers that require a threat modelling session:
- A new trust boundary. A new third-party integration, a new service-to-service call, a new client type, a webhook receiver, a new queue consumer.
- A new class of data. Anything that is personal, financial, health-related, or a credential. The question is not volume but category.
- Any change to authentication or authorisation. New role, new sharing model, new token type, new permission check.
- Any feature that accepts user-supplied input used to reach something else — URLs, file uploads, template strings, database identifiers. This one catches SSRF, injection and access-control bugs as a class.
- Any feature that sends something to a person — email, SMS, notification — because those are abuse channels.
Explicitly not triggers: UI changes, copy changes, refactors that do not move a boundary, performance work, adding a field to an existing model of the same class of data.
How to keep it cheap:
- Timebox it to 30 minutes with two or three people, at design time. The output is a handful of decisions in the ticket, not a document.
- Put the trigger list in the pull-request template as a checkbox, so the question is asked by the process rather than remembered by a person.
- Keep a running list of accepted risks with review dates, so acceptance does not silently become permanence.
The thing that makes this work: the list must be short enough to read in ten seconds. A comprehensive list is a list nobody consults.
Key takeaways
- Threat modelling answers what are we building, what can go wrong, what are we doing about it, and did we do enough — in that order. Skipping to the third produces defences that answer no question.
- Draw trust boundaries, not architecture. A boundary is any place an assumption is made about the other side.
- Enumerate with a checklist such as STRIDE, because unaided imagination produces the same three threats every time.
- Rank by consequence and ease of exploitation. Nearly all real breaches are cheap attacks on valuable things.
- Mitigate, eliminate, transfer or accept — and record acceptances, or they become oversights in hindsight.
- Do it when the design is still a conversation, and re-do it whenever a boundary changes.
Further reading
- Adam Shostack, Threat Modeling: Designing for Security (Wiley, 2014) — the standard text, and the source of the four questions.
- Loren Kohnfelder and Praerit Garg, "The Threats to Our Products" (Microsoft internal memo, 1999) — where STRIDE was defined.
- Threat Modeling Manifesto (2020) — a short statement of values by practitioners across the field.
- Ross Anderson, Security Engineering, third edition (Wiley, 2020), chapter 1 — what goes wrong, and why, with historical cases.
- Bruce Schneier, "Attack Trees" (Dr. Dobb's Journal, 1999) — an alternative structure for enumerating how a goal can be reached.