The Only Threat Model That Survives Is One Page
A threat model that works for a small engineering team is one page: a data flow diagram with trust boundaries drawn honestly, plus a short ledger of decisions, stored in the repository next to the code it describes. Anything heavier gets produced once, admired in a slide deck, and never opened again. The ceremony version, with its half-day workshop, dedicated tooling and heat-mapped risk register, fails not because the ideas are wrong but because its maintenance cost exceeds what a team of five can pay while shipping.
The purpose of the exercise is also narrower than the methodology literature suggests. You are not enumerating every threat in the universe. You are finding the places where an attacker's view of the system differs from yours, and those places cluster at trust boundaries. A diagram that shows every arrow crossing a boundary, annotated with the credential that authorises it, surfaces most of what a formal methodology would find, for a fraction of the effort.
Draw the System You Actually Run
The notation is deliberately small. External entities are the things you don't control: users, third-party APIs, webhook senders, the public internet. Processes are your running code. Data stores are anywhere state persists: databases, blob storage, queues, log aggregators, backups. Arrows are data flows between them. That is the entire vocabulary of a data flow diagram, and it fits on a whiteboard in twenty minutes.
The common failure is drawing the architecture you intended rather than the one you operate. The intended system is a browser, an API and a database. The real system also includes the CI pipeline that holds deployment credentials, the cron jobs that run with the API's full database access, the admin panel bolted on in week three, the support script someone runs against production, and the third-party webhook that calls into your API unauthenticated because integration day was a rush. Every one of those is an arrow, and the arrows nobody drew are where incidents come from.
Two disciplines keep the drawing honest. First, every arrow gets a protocol and a credential: HTTPS with a session cookie, AMQP with a shared connection string, SSH with a key on someone's laptop. If you cannot name the credential that authorises an arrow, you have found your first issue before STRIDE enters the room. Second, never draw the cloud as one box. Split it into the resources that hold data or execute code, because an app service using a managed identity and an app service holding a connection string in its settings are different systems with different failure modes.
Trust Boundaries Are Where the Findings Live
A trust boundary is any line where the level of trust changes: internet to application edge, application to database, one tenant's data to another's, a developer laptop to production. The obvious ones get drawn. The ones small teams miss are operational: the CI runner that can deploy anything, the shared admin account whose password lives in three heads, the support tooling that reads production data from outside production. Draw those lines even when they are embarrassing, especially when they are embarrassing, because an attacker will not politely ignore them.
In multi-tenant software the tenant boundary deserves its own colour. It is usually enforced in code, through query predicates or row-level security, rather than by infrastructure, which makes it the most fragile boundary in the system: every new query is a fresh opportunity to cross it. It is also the boundary a reviewer verifies line by line, and reading pull requests with the boundary map open is a large part of what a secure code review engagement actually does.
Some boundaries are legal as well as technical. Where a store holds personal information, APP 11 of the Privacy Act 1988 requires reasonable steps to protect it, and the Notifiable Data Breaches scheme means an unauthorised crossing can end in a notification to the OAIC and to the individuals affected. Marking data classes on your stores (personal information, credentials, payment references) costs one extra annotation and changes how every boundary around them is prioritised.
STRIDE Is a Prompt List, Not a Taxonomy
STRIDE stands for spoofing, tampering, repudiation, information disclosure, denial of service and elevation of privilege. Used badly, it becomes a classification debate about whether a finding is tampering or elevation, and nobody cares. The categories exist to prompt a question at each boundary crossing, and the finding is the answer, not the label. Five minutes per crossing, six questions, write down what falls out.
Prioritise without formal scoring. Three buckets are enough: fix now, for anything internet-reachable whose blast radius includes the tenant boundary or a credential store; fix on next touch, for findings inside code you will be in anyway; accept and record, with a date and a rationale. A five-person team arguing over decimal points in a risk score is spending its security budget on the spreadsheet. The theatre always tries to creep back in through the scoring.
- Spoofing: what stops a caller pretending to be this entity, and which credential would they need to steal?
- Tampering: can data on this arrow or in this store be altered without anyone detecting it?
- Repudiation: if this action were done maliciously, would the logs prove who did it?
- Information disclosure: who else can read this flow or this store, including backups, logs and error messages?
- Denial of service: what happens here under load or malformed input, and who finds out?
- Elevation of privilege: does anything on the far side of this arrow run with more privilege than the caller needs?
Keep the Model Alive by Making It Diff
Threat models die because updating them is nobody's job. The fix is to make the model part of the code. Store the diagram as text, in Mermaid or any diagram-as-code format, in the same repository as the system, so a change to the architecture shows up as a diff in a pull request and a reviewer can watch a new arrow appear. Add one question to the pull request template: does this change add an arrow, a data store, or a trust boundary? If yes, the diagram changes in the same pull request, not in a quarterly review that never happens.
Re-model at architectural moments, not on a calendar. A new third-party integration, a new ingress path, the first background worker, and above all a new class of data: the day the system first stores personal information or payment references, the model is out of date no matter how recently you reviewed it. A new data class changes the stakes of every existing boundary; a new feature usually doesn't.
Keep the accepted-risk ledger in the same folder as the diagram so it gets re-read whenever the model does. An accepted risk with a date, an owner and a rationale is a decision. Without them it is just a vulnerability with better paperwork.
The First Hour, and What Good Looks Like Later
If you have nothing today, the first pass takes an hour. Thirty minutes to draw the data flow diagram with real arrows and named credentials. Ten minutes to mark the trust boundaries, including the operational ones. Twenty minutes to run the six STRIDE questions against the two boundaries that face the internet. Consider a team doing this for the first time: the likely harvest is a webhook endpoint that trusts whoever calls it, a CI token with write access to production, and application logs quietly accumulating personal information. None of those needs a methodology to fix. They needed someone to draw the arrow.
Six months in, good looks unglamorous. The diagram is still one page and still matches production. The ledger has dates on it. Code reviews cite boundaries by name, and a new engineer can read the model in ten minutes and know exactly where the system demands care. That is the entire return on the exercise, and it is worth more than any workshop: a shared, current picture of where trust changes hands, cheap enough that keeping it true is nobody's burden and everybody's habit.
