You cannot find threats in a system you cannot see. Learn to draw a data flow diagram with the four DFD elements and — the part that matters — mark the trust boundaries where threats concentrate.
A data flow diagram (DFD) is the lingua franca of threat modeling because threats attach to its elements. Keep it simple: four shapes. Draw at the level where each element is a thing you could attack or that enforces a control — usually one diagram per service or feature, not the whole company.
EXTERNAL ENTITY a user or system you don't control (rectangle)
PROCESS your code that acts on data (circle)
DATA STORE where data rests (two lines)
DATA FLOW data moving between the above (arrow)
Rule of thumb: if untrusted data crosses into something you
own, that crossing is where the interesting threats live.A trust boundary is any line where the level of trust changes: the internet meeting your load balancer, your app calling a third-party API, a container talking to the host, a low-privilege service reaching a database. Threats cluster on these lines because that is where an assumption ("this input is safe") can be violated. Marking them well is 80% of the value of the diagram.
internet | your VPC
|
[Browser] --HTTPS--->( API )----> ( Auth svc )
| |
| +------->[ Postgres ]
| |
| +------->( Payments API ) --> 3rd party
| |
trust boundary ---+ another boundary -+
Every arrow that CROSSES a boundary is a candidate threat:
untrusted input, missing authn/authz, data leaving your control.Model a concrete slice rather than an abstraction. Here is a DFD for "user uploads a profile picture" — a feature that looks trivial but crosses three trust boundaries and touches storage, a processing worker, and a CDN. Drawing it this precisely is what surfaces the threats in the next lesson.
EXTERNAL: User (browser) — untrusted
PROCESS : Upload API — validates + stores
DATASTORE: Object storage (S3) — holds raw + resized images
PROCESS : Image worker — resizes, strips EXIF
EXTERNAL: CDN — serves public images
Flows (each crosses a boundary — note what is assumed safe):
User --(multipart file)--> Upload API [size? type? magic bytes?]
Upload API --(put object)--> S3 [path from user input?]
Image worker --(get object)--> S3 [processes attacker bytes]
Image worker --(resized)--> S3
CDN --(get object)--> S3 [is the bucket public?]
User <--(image URL)-- Upload APIThe most dangerous diagram is one that is subtly wrong — it hides real flows and invents fake ones. Draw what the system actually does, including the ugly parts (the debug endpoint, the shared admin key, the direct DB access from a cron job). Prefer diagrams-as-code so the model lives in the repo and changes in the same PR as the design.
# threat-model/upload.dfd (Mermaid — renders in GitHub, lives in the repo)
flowchart LR
User[User browser]:::ext -->|multipart file| API((Upload API))
API -->|put object| S3[(Object storage)]
Worker((Image worker)) -->|get/put| S3
CDN[CDN]:::ext -->|get object| S3
subgraph trust: our VPC
API
Worker
S3
end
classDef ext fill:#fdd,stroke:#900;