Most threat models are written once. A team books a workshop, draws the system, lists threats with STRIDE, and produces a document. It is often a good document. Six months later the product has new integrations, a new deployment path and two new roles, and the document describes a system that no longer exists.
Our position is that the problem is not discipline. It is shape. A single document mixes things that change at very different speeds and are owned by different people. Split it into layers and it can keep up.
Why one document fails
Think about who knows what in a typical organisation. Product knows which flows move money and which data is sensitive. Architects know the services and boundaries. Developers know the code paths. Platform knows the pipeline and its tokens. Operations knows what is actually running and who can reach it.
A single threat model asks all of them to update one artefact. In practice, nobody does, because no single change feels big enough to reopen the whole thing. So the model drifts until the next audit or incident forces a rewrite.
There is a second problem. A threat at one level often has its cause at another. An authorisation bug in code may exist because the design never stated who owns an object. A supply chain risk in the pipeline may matter only because the runtime has broad cloud permissions. A flat list of threats hides those links.
The five layers
We keep threat models in five layers. Each one has an owner and a clear trigger for an update:
| Layer | What it holds | Owner | Update when |
|---|---|---|---|
| Business and process | Valuable assets, critical flows, abuse goals, actors | Product | A new feature, market or role |
| Architecture | Services, data stores, trust boundaries, data flows | Architects or tech leads | A new service, integration or store |
| Code | Entry points, authorisation logic, parsers, crypto use | Service teams | Changes to sensitive paths |
| Pipeline | Runners, tokens, actions, dependencies, signing | Platform | A new workflow, secret or deploy target |
| Runtime | Identities, network reach, exposed ports, detection | Operations or SRE | New infrastructure or permission changes |
Each layer is small enough to review in an hour, and each one changes when its owner already has the context in their head.
Threats that cross layers
The layers are not separate documents that ignore each other. The value is in the links. A threat is recorded at the layer where it is realised and linked to the layers that enable or limit it. For example:
- BusinessGoal: a customer reads another customer's statements.
- ArchitectureStatements service shares a database across tenants, filtered by a tenant id.
- CodeThe export endpoint takes a statement id and must check its tenant on every call.
- RuntimeExport calls are logged with tenant and actor, and alert on cross-tenant patterns.
Now a change in any layer tells you what to recheck. If the architecture moves to per-tenant databases, the code-level check becomes defence in depth rather than the only control. If someone adds a bulk export, the code layer owner sees that a business-layer goal depends on it.
The usual objections
"This is more work." It is less work spread more evenly. Updating one layer after a change takes minutes. Rewriting a whole model every year takes days and is never quite finished.
"Our teams will not keep it up." They will not keep up a document. They will keep up a section that is triggered by changes they already make, especially if the pull request template asks the question.
"We do not have a security team to run it." The layers are owned by the people who know them. Security, if you have it, reviews the links and the gaps, which is where expertise matters most.
What we would do
- Start with the business layer. Five to ten abuse goals in plain language are enough.
- Draw the architecture layer on one page with trust boundaries marked.
- For code, pipeline and runtime, start with only the parts linked to those goals. Do not try to model everything.
- Store each layer as text in the repository closest to its owner, and link them by id.
- Add a line to pull request and change templates: "Does this change a threat model layer?"
- Review the links once a quarter. Unlinked goals and orphaned controls are the agenda.
This is the idea behind the layered threat model we are building into the tower: each layer updates from the systems that already describe it, and the links show what a change touches. You do not need a platform to start, though. A folder of short text files and a habit will get you most of the way down the line.
Where this sits on the line
In the tower (in development):