In a recent post I covered how attacking a deliberately vulnerable API can better drive home the impact of the OWASP API Security Top 10. That’s the reactive half of security: taking something that already exists and probing it for the mistakes introduced during design and development. It’s necessary work, but it’s also the expensive place to find a flaw. By the time a broken authorisation check or an over-trusting endpoint reaches a penetration test, it’s already been designed, written, reviewed and deployed. The cheapest vulnerability to fix is the one you designed out before a line of code was written — and that’s what threat modelling is for.
Threat modelling is the practice of thinking, deliberately and early, about how a system can be attacked, so that the risks can be understood and addressed while they’re still cheap to address. It’s not a tool you buy or a scan you run; it’s a structured conversation about a design. And like any good conversation, it benefits from a few clear questions to keep it on track.
It’s also worth saying plainly that this isn’t a software-only technique. The examples in this post lean on web applications and APIs because that’s where the previous post left off, but the same approach applies just as well to cloud infrastructure, network architecture, endpoints, operational technology — anything with assets worth protecting, actors that interact with it, and boundaries where the level of trust changes. A cloud environment, a segmented network, a fleet of laptops: each can and should be modelled the same way. The vocabulary shifts a little from domain to domain, but the four questions don’t.
Four questions
Whatever approach or tooling you use, it helps to anchor the whole exercise in four plain questions:
- What are we building?
- What can go wrong?
- What should we do about the things that can go wrong?
- Did we do a good job of the analysis?
Everything else is just structure in service of answering those four as best you can. The first is about understanding the system; the second about enumerating threats; the third about deciding what to do; the fourth about checking your work.
What are we building? Start with a diagram
You can’t reason about what you can’t see, so the first step is to draw the system as a data flow diagram. A DFD captures the pieces that matter for security: the actors (a user’s browser, a third-party service), the processes that do the work, the data stores where information lives, the data flows that move it around, and — most importantly — the trust boundaries that separate them.
A trust boundary is simply the line where the level of trust changes: where you can no longer assume the thing on the other side is friendly. The boundary between the public internet and your API, between your application and its database, between one microservice and another — each is a place where data crosses from one level of trust to a different one, and each is where interesting things tend to go wrong. A free, open-source tool such as OWASP Threat Dragon is enough to draw the diagram and record threats against it.
What can go wrong? STRIDE-LM
With a diagram in hand, you need a systematic way to ask “what can go wrong” at each element and each boundary — because unaided brainstorming misses things. This is what a mnemonic like STRIDE is for. STRIDE was developed at Microsoft, and each letter maps a category of threat to the security property it violates:
| Threat | Property violated | Definition |
|---|---|---|
| Spoofing | Authentication | Impersonating something or someone else |
| Tampering | Integrity | Malicious or unintended modification of data or code |
| Repudiation | Non-repudiation | Claiming not to have performed an action |
| Information disclosure | Confidentiality | Exposing information to someone not authorised to see it |
| Denial of service | Availability | Denying or degrading service to legitimate users |
| Elevation of privilege | Authorisation | Gaining capabilities without proper authorisation |
| Lateral Movement | Containment | Expanding control beyond the initial point of compromise |
I use the STRIDE-LM variant, which adds Lateral Movement — a reminder that an attacker’s first foothold is rarely their last, and that containment between components matters as much as keeping them out in the first place. Go through each element of the diagram against each letter: can this data flow be spoofed? Tampered with? Can this process be made to deny service?
A few habits make the enumeration better. Start with the external entities — the untrusted edges are where attackers start too. Never dismiss a threat out of hand; write it down, even if you decide later it’s not worth acting on. But do focus on the feasible, and at a minimum reason about the well-known threat classes — the OWASP Top Ten, the OWASP API Security Top 10, and the CWE Top 25 — and lean on established frameworks such as MITRE ATT&CK and the OWASP Application Security Verification Standard rather than working from imagination alone.
Don’t forget privacy: LINDDUN
STRIDE is aimed at security threats, but a system that handles personal data can harm people without ever being “hacked” in the traditional sense — through excessive collection, needless linkability, or simply failing to give people a say in how their data is used. For that there’s LINDDUN, a mnemonic aimed specifically at privacy threats: Linking, Identifying, Non-repudiation, Detecting, Data disclosure, Unawareness, and Non-compliance. Running a privacy pass alongside the security one is what turns “is it secure?” into the fuller question of “is it secure and respectful of the people whose data it holds?” — and, increasingly, what keeps you on the right side of a growing number of data-protection laws.
What should we do about it?
Every threat you have recorded needs a decision, and there are only four honest options: mitigate it, accept it, transfer it, or avoid it. Most get mitigated, and when they do, it’s worth writing down how — TLS for a tampering-in-transit threat, authentication and authorisation for spoofing and elevation, input validation for injection, logging for repudiation, and so on.
It helps to have a mental checklist of the places mitigations live. Broadly, web application defences fall into recurring categories: authentication (proving who someone is), authorisation (deciding what they may do), input validation (never trusting what comes in), communication security and cryptography (protecting data in transit and at rest), session management, sensitive-data handling, configuration management, exception management (failing safely and quietly), and auditing and logging (knowing who did what, and when). When a threat has no obvious home in one of these, that’s often a sign it deserves more thought, not less. Other domains have their own equivalent families — network segmentation and firewalling, identity and least-privilege access in the cloud, hardening and detection on endpoints — but the instinct is identical: know the standard places a control can sit, and hold each threat up against them.
Did we do a good job? Test, and keep it alive
The last question is the one most easily skipped. A threat model is a set of claims — “we mitigated this, and here’s how” — and claims should be tested. For each threat you addressed, there should be a check, manual or automated, that would catch the problem if the mitigation ever failed or regressed. This is exactly where the reactive work from the API post comes back in: the threat model tells you what to test, and penetration testing and DAST are how you validate that the mitigations actually hold. The two halves are complementary — modelling without testing is wishful thinking, and testing without modelling is a search with no map.
Finally, a threat model isn’t a document you write once and file away. It should live in source control alongside the thing it describes, and be revisited whenever the design changes materially. Systems evolve, trust boundaries move, and a model that no longer reflects reality is worse than none, because it lends false confidence.
Closing the loop
Pen-testing and threat modelling are two ends of the same discipline. One asks “what did we get wrong?” of a system that already exists; the other asks “what could we get wrong?” of one still on the drawing board. Do only the first and you’re forever paying full price to fix things late. Do the first informed by the second and you find far fewer surprises at the end — because you spent a little time, early and deliberately, determining how things could go wrong, and did something about it.
