drawing-threat-models
Skill2 starsChanged 2 months agoMITView on GitHub
Use when drawing a threat model or data flow diagram — external entities, processes, data stores, data flows, trust boundaries, and STRIDE threats on the flows that cross them — as a .drawio / draw.io file. Also called a DFD, security architecture review or STRIDE diagram, and for fixing one that has no boundaries or whose flows are unlabelled
In this repository5 other files it gives its agents.
The file
We split this skill into the rules it gives an agent
3 rules. About 0 are common, 0 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 27
Use the folder the user names.
- Very specific: line 34
Never leave a build script, a .drawio or a preview in the project root, and never write outside the project you were pointed at.
- Very specific: line 49
Every element takes an optional icon_name= — the same diagrams library the architecture skill uses.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Drawing threat models
A .drawio data flow diagram for threat modelling, in the same visual
language as the other drawing-* skills. Four element kinds, trust boundaries,
and a numbered threat table under the picture.
Build it with dfd_drawio.py (this directory). Check it with
check_routing.py. Look at it with preview.py.
Where the files go
Everything this skill produces lives in one folder, never loose in the project root:
docs/diagrams/
api-threat-model.py # the script that builds it
api-threat-model.drawio # the diagram
api-threat-model.preview.svg # what you looked atUse the folder the user names. Otherwise put it under docs/diagrams/ when the
project has a docs/ directory, and diagrams/ at the project root when it
does not — the build script, the .drawio, and any preview or PNG together.
write() creates the folder if it is missing, so
f.write("docs/diagrams/api-threat-model.drawio") is all it takes, and preview.py
writes the SVG beside its input.
Never leave a build script, a .drawio or a preview in the project root, and
never write outside the project you were pointed at. One folder, named files —
a diagram whose script sits next to it can be regenerated; one scattered across
a repository root cannot be found again.
The elements
entity | white box — something outside the system: a person, a partner, another company. You cannot change it and cannot trust it. |
process | ellipse — something the system does. Code you own. |
store | two rules — something at rest: a database, a bucket, a queue, a log. |
flow | an arrow — data moving, labelled with what moves |
boundary | a dashed red frame — where trust changes |
Every element takes an optional icon_name= — the same diagrams library the
architecture skill uses. Find one with:
python3 ~/.claude/skills/drawing-threat-models/dfd_drawio.py bigqueryA process is an ellipse, which is narrowest exactly where a label is widest, so it keeps 30px at the sides against a box's 12. With an icon the label reads from the left of what is left; without one it is centred.
The output contract
- Every flow says what data moves.
flow()requires a label. "session cookie" and "card number" crossing the same boundary are not the same risk, and an unlabelled arrow cannot be threat-modelled at all. - At least one trust boundary. A DFD with no boundary is a data flow diagram, not a threat model — the boundaries are what turn it into one.
- Every flow that crosses a boundary is examined. Mark it
crosses=; it turns red, which is a prompt, not a verdict. STRIDE is applied per element, and a crossing is where the interesting ones live. check_routing.pyreporting 0 FAIL.
The pieces
from dfd_drawio import Model, L, R, T, B
m = Model("Checkout", "STRIDE · v2")
user = m.entity("Shopper", "browser", col=0, row=0)
api = m.process("Orders API", "Cloud Run", col=1, row=0)
db = m.store("Cloud SQL", "orders, PII", col=1, row=1)
net = m.boundary("Internet | DMZ", cols=[0, 0], rows=[0, 1])
gcp = m.boundary("Google Cloud project", cols=[1, 2], rows=[0, 1])
f1 = m.flow(user, api, "credentials + cart", exit=R(), entry=L(),
direct=True, crosses=net)
m.threat(f1, "S", "Credential stuffing against the login endpoint",
mitigation="Device check + per-identity rate limit")threat() takes a STRIDE letter, numbers itself, puts a red disc on the flow
and writes the row in the table underneath. The letter alone is a quiz, so the
table spells it out: Spoofing · credentials + cart — …, with the
mitigation in green where there is one. A threat with no mitigation stays
black, which is the point of writing it down.
| letter | protects | |
| S | Spoofing | authentication |
| T | Tampering | integrity |
| R | Repudiation | non-repudiation |
| I | Information disclosure | confidentiality |
| D | Denial of service | availability |
| E | Elevation of privilege | authorisation |
Geometry
| element | 200 × 64 |
| column pitch | 360, row pitch 210 |
| boundary | 34 outside the elements it holds |
The page measures itself, and leaves room under the picture for the table.
Before handing it over
S=~/.claude/skills/drawing-threat-models
python3 "$S/check_routing.py" docs/diagrams/model.drawio # must report 0 FAIL
python3 "$S/preview.py" docs/diagrams/model.drawio # writes docs/diagrams/model.preview.svg
rsvg-convert -w 1400 -o docs/diagrams/model.png docs/diagrams/model.preview.svgThen read it for what a checker cannot see: a store nothing writes to, a process with no inputs, a boundary drawn where no trust actually changes, a flow crossing a boundary with no threat listed against it.
Common mistakes
| No trust boundaries | Then it is a data flow diagram. The boundaries are the model. |
| A boundary around everything | If nothing crosses it, it says nothing. Draw it where trust actually changes. |
| Flows labelled with a verb | "sends" is not data. Say what moves: "card token, amount". |
| Threats with no mitigation and no owner | Fine as a first pass, and the table shows them in black so they stand out. Do not ship the model that way. |
| STRIDE applied to the diagram | It is applied per element and per flow. Six letters against each crossing beats a list of six for the whole picture. |
| A process you do not own drawn as a process | If you cannot change it, it is an external entity, and that changes every threat about it. |
preview.py writes a plain SVG — open it in any browser if you have no rsvg-convert. The PNG step is for pasting into a document, not for looking at it.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

