Skip to content
Security6 min read

Threat modelling in an hour, with four questions and a whiteboard

Threat modelling has a reputation for heavyweight documents nobody reads. The useful version is a one-hour conversation over a diagram, answering four questions, producing a short list of decisions — and it catches the class of flaw that no scanner will ever report, because the code is working exactly as written.

Part ofCybersecurity & Information Security

Automated tooling finds implementation bugs: an injectable query, a vulnerable dependency, a missing header. It cannot find design flaws, because a design flaw is code doing precisely what it was asked to do. An endpoint that returns any record when given its identifier is not a bug in any scanner's model — it is a missing authorisation decision, and only a person reasoning about the design will notice.

The four questions

  1. What are we building? Draw it. Not the class diagram — the data flows, the trust boundaries, and where data is stored.
  2. What can go wrong? Walk the boundaries systematically rather than brainstorming freely.
  3. What are we going to do about it? Every finding gets one of four dispositions, with a name attached.
  4. Did we do a good job? Revisit when the design changes, and check whether the mitigations were actually built.

That framing comes from the threat-modelling community and it is deliberately unsophisticated. Its value is that a mixed group — a backend engineer, someone who knows the product, someone who knows the infrastructure — can work through it without prior training.

Draw the trust boundaries, not the architecture

Most architecture diagrams are useless for this because they show components rather than the lines data crosses. A threat-model diagram needs exactly four things: external actors, processes, data stores, and boundaries between zones of differing trust.

text
                    ┌─── trust boundary: the internet ───┐
[ Visitor ]────────▶│  CDN / edge  ────▶  App (SSR + API) │
[ Attacker ]───────▶│                          │          │
                    └──────────────────────────┼──────────┘
                                               │
                    ┌─── boundary: private network ───────┐
                    │   Postgres        Object storage    │
                    │   (tenant data)   (uploads)         │
                    └─────────────────────────────────────┘
                                               │
                    ┌─── boundary: third parties ─────────┐
                    │   Payments    Email    LLM provider │
                    └─────────────────────────────────────┘

Every arrow crossing a boundary is where the interesting
questions live. Arrows inside a boundary rarely produce findings.

STRIDE, per boundary crossing

Free-form brainstorming finds whatever the loudest person in the room is worried about. A checklist finds the categories nobody thought of. STRIDE is the standard one, and running it per arrow rather than per system is what makes it tractable.

CategoryThe question to askTypical mitigation
SpoofingCan someone claim to be another identity?Authentication, signed requests, mutual TLS
TamperingCan data be modified in transit or at rest?TLS, integrity checks, server-side validation
RepudiationCan someone deny having done it?Append-only audit log with actor and time
Information disclosureCan data reach someone unauthorised?Authorisation per record, encryption, field redaction
Denial of serviceCan one caller exhaust a shared resource?Rate limits, quotas, timeouts, payload caps
Elevation of privilegeCan a caller gain rights they were not granted?Deny by default, server-side role checks

In practice two categories produce most of the findings. Information disclosure catches the endpoint that trusts an identifier from the client; elevation of privilege catches the check performed in the interface but not on the server. Both are design flaws, and both are invisible to scanners.

Four dispositions, each with a name attached

A finding with no disposition becomes a line in a document nobody revisits. Force one of four, and record who decided.

DispositionMeansRequirement
MitigateBuild a controlA ticket that exists and is scheduled
EliminateRemove the feature or the dataOften the cheapest option, and rarely considered
TransferSomeone else carries itA named provider or insurer, not a vague assumption
AcceptLive with it, knowinglyA named person, a date, and a stated reason

Accepting risk is legitimate and underused — plenty of findings genuinely are not worth the control. What is not legitimate is accepting it implicitly. An accepted risk with a name and a date is a decision; an unassigned one is an oversight waiting to be discovered by someone else.

Eliminate deserves more attention than it gets. A field you stop collecting cannot leak, and a feature you remove needs no control. Data minimisation is the strongest available security measure and it also reduces obligations under data-protection law.

When to run one

  • New service or major feature — before implementation, while the design is still cheap to change
  • New trust boundary — a new third party, a new integration, a new class of user
  • Handling a new category of data — payments, health, biometric, anything that changes the obligations
  • After an incident — model the neighbourhood of what happened, not only the specific fault
  • Annually for critical paths — because the system changed even where the diagram did not

What the output should look like

A page. The diagram, and a table of findings with dispositions and owners. Anything longer will not be read, and anything shorter loses the reasoning that made the decisions defensible.

text
Threat model — Tenant file uploads      Reviewed: 2026-09-23
Participants: <names>

#  Boundary crossing        Category      Finding                          Disposition
1  Browser → upload API     Info. disc.   Download URL is guessable        Mitigate — signed, expiring URL
2  Browser → upload API     DoS           No size or rate cap              Mitigate — 10MB cap, 20/hour
3  App → object storage     Tampering     Bucket allows public read        Mitigate — private + short-lived URL
4  Upload → virus scan      —             No scanning at all               Accept — <name>, review 2027-03
5  Browser → upload API     Elev. of priv Tenant id read from the request  Mitigate — derive from session

Next review: on any change to storage layout or sharing features.

Finding 5 in that example is the one worth noticing. Taking the tenant identifier from the request body rather than the session is a single line of code and a complete cross-tenant data breach, and it is exactly the kind of thing an hour with a diagram catches while a test suite happily passes.

Questions

How long should a threat model take?

One hour for a feature, two or three for a new service. The lightweight version exists because a session that fits in an hour actually happens before implementation, which is where its value lies. A comprehensive model produced after launch generates a backlog rather than changing a design.

Why not rely on scanners and penetration tests?

They find different things. Scanners find implementation bugs — injectable queries, vulnerable dependencies, missing headers. A design flaw is code working exactly as specified, so no scanner will report it. An endpoint that returns any record when given its identifier has no bug in it; it has a missing authorisation decision, and only a person reasoning about the design will notice.

Do we need a security specialist in the room?

It helps and it is not a prerequisite. The four questions and the STRIDE checklist are designed to be workable by a mixed engineering group. What matters more is having someone who understands the data flows, someone who understands the infrastructure, and a willingness to write down accepted risks with a name against them.

What is the most common finding in practice?

An endpoint that accepts an identifier from the client and returns the corresponding record without checking that this caller is entitled to it. Tests pass because test users request their own records. The related pattern is deriving a tenant or organisation identifier from the request body rather than the session, which turns one line of code into a cross-tenant breach.

Want this applied to your systems?

Most of what is described here begins as an assessment: a fixed-scope review producing a prioritised written report you can act on with any supplier.

Prefer email? hello@kornspire.com