Learning Goals
3 minBy the end of this lesson you can:
- Use the four threat-modelling questions to analyse any system.
- Apply STRIDE to categorise threats.
- Identify trust boundaries and attack surface in a design.
- Rank threats by risk and pair each with a mitigation.
Warm-Up · Where Would You Attack?
5 minPicture a simple login page that checks a username and password against a database. An attacker doesn't see "a login form" — they see a list of opportunities:
- Can I guess passwords (no rate limit)?
- Can I inject SQL into the username field?
- Can I read the password database if I get in?
- Is the traffic encrypted, or can I sniff it?
- Can I reset someone else's password?
Threat modelling is structured paranoia. Instead of hoping you thought of everything, you walk a framework over your design so the weaknesses surface on purpose. The four questions are: What are we building? What can go wrong? What will we do about it? Did we do a good job? STRIDE answers the second one systematically.
New Concept · STRIDE, Boundaries & Surface
14 minSTRIDE — the six threat categories
S Spoofing pretending to be someone you're not → maps to: Authentication T Tampering altering data or code → Integrity R Repudiation denying you did something (no proof) → Non-repudiation / logging I Information disclosure leaking data you shouldn't see → Confidentiality D Denial of service making the system unavailable → Availability E Elevation of privilege gaining rights you shouldn't have → Authorization
For each component of your system, ask the six STRIDE questions. "Could someone spoof this? tamper with it? deny doing it? leak its data? deny service to it? elevate privileges through it?" You'll be surprised what surfaces.
Trust boundaries
A trust boundary is any line where data crosses from a less-trusted zone to a more-trusted one — and where you must not trust the input.
[ Browser ] ──── trust boundary ────► [ Your server ] (anyone can send anything) (must validate everything) [ Your app ] ──── trust boundary ────► [ Database ] [ Internet ] ──── trust boundary ────► [ Internal network ]
The classic rule: validate at the boundary. SQL injection, XSS, and command injection all happen because untrusted input crossed a boundary without being checked. Mark every boundary in a design — that's where most threats live.
Attack surface
The attack surface is the sum of all the places an attacker can interact with your system: every input field, API endpoint, open port, file upload, query parameter, and dependency. A core defensive principle is to minimise it — fewer entry points, fewer things to get wrong.
- Close ports you don't use.
- Remove endpoints and features you don't need.
- Drop dependencies you don't use (each is attack surface — Lesson 35).
Ranking by risk
# a simple risk score: how likely × how bad (1-5 each) def risk(likelihood: int, impact: int) -> int: return likelihood * impact # 1 (trivial) … 25 (critical) threats = [ ("SQL injection on login", 5, 5), # easy + total compromise ("Weak password policy", 4, 4), ("Verbose error pages leak stack", 3, 2), ("DoS via huge upload", 2, 3), ] for name, l, i in sorted(threats, key=lambda t: risk(t[1], t[2]), reverse=True): print(f"{risk(l, i):2} {name}")
25 SQL injection on login 16 Weak password policy 6 Verbose error pages leak stack 6 DoS via huge upload
You can't fix everything at once, so rank: likelihood × impact. Fix the 25s before the 6s. This turns a vague list of fears into a prioritised work plan.
Worked Example · Threat-Model a Login System
12 minLet's model the login system from the warm-up, end to end, producing a ranked threat table with mitigations — the actual deliverable of a threat-modelling session.
SYSTEM (Q1: what are we building?)
Browser → [TLS] → Flask /login → SQL query → users table
▲ trust boundary here (untrusted input enters)
THREATS via STRIDE (Q2: what can go wrong?) + risk (L×I):threat_model = [ # (STRIDE, threat, likelihood, impact, mitigation) ("S", "credential stuffing / brute force", 5, 4, "rate limit + lockout + MFA"), ("T", "SQL injection in username field", 5, 5, "parameterised queries (L8-31)"), ("I", "password DB stolen → plaintext leak", 3, 5, "bcrypt-hash passwords (L8-13)"), ("I", "traffic sniffed on open wifi", 3, 4, "enforce HTTPS/TLS (L8-09)"), ("R", "user denies a malicious action", 2, 3, "audit logging (L8-45)"), ("E", "low-priv user reaches admin route", 3, 5, "RBAC access control (L8-40)"), ("D", "login flooded, service down", 2, 3, "rate limit + CAPTCHA"), ] def risk(l, i): return l * i print(f"{'risk':>4} {'cat':3} threat → mitigation") for cat, threat, l, i, fix in sorted( threat_model, key=lambda t: risk(t[2], t[3]), reverse=True): print(f"{risk(l, i):>4} [{cat}] {threat}\n → {fix}")
risk cat threat → mitigation
25 [T] SQL injection in username field
→ parameterised queries (L8-31)
20 [S] credential stuffing / brute force
→ rate limit + lockout + MFA
15 [I] password DB stolen → plaintext leak
→ bcrypt-hash passwords (L8-13)
15 [E] low-priv user reaches admin route
→ RBAC access control (L8-40)
12 [I] traffic sniffed on open wifi
→ enforce HTTPS/TLS (L8-09)
6 [R] user denies a malicious action → audit logging (L8-45)
6 [D] login flooded, service down → rate limit + CAPTCHARead the model
Notice three things. First, STRIDE made us consider threats we'd have skipped (repudiation, elevation) — the framework catches what intuition misses. Second, every threat has a concrete mitigation, most of which are later lessons in this very level — threat modelling is literally the map of Level 8. Third, the ranking tells us SQL injection and brute force come first. A threat model isn't paperwork; it's a prioritised to-do list for making something safe.
Try It Yourself
13 minDraw (on paper) the data flow of a file-upload feature: browser → server → disk → virus scan → database record. Mark every trust boundary with a line and label what must be validated there.
Pick one feature of an app you use (a comment box, a payment, a search). Walk all six STRIDE categories and write at least one concrete threat for four of them.
Example (a comment box)
S post a comment as another user (forged identity) T edit someone else's comment I comment reveals an internal user ID in the HTML D paste 10MB of text to crash rendering E comment contains a script that runs as the admin (stored XSS)
Write rank(threats) that takes a list of (name, likelihood, impact) tuples and prints them sorted by risk with a severity label (≥20 CRITICAL, ≥12 HIGH, ≥6 MEDIUM, else LOW). Run it on your STRIDE list from exercise 2.
Hint
def severity(score): return ("CRITICAL" if score >= 20 else "HIGH" if score >= 12 else "MEDIUM" if score >= 6 else "LOW") def rank(threats): for name, l, i in sorted(threats, key=lambda t: t[1]*t[2], reverse=True): s = l * i print(f"{s:2} {severity(s):8} {name}")
Mini-Challenge · Model One of Your Level 7 Tools
8 minTake an automation you built in Level 7 (the API client, the form bot, the server toolkit). Produce a one-page threat model: the data flow with trust boundaries, a STRIDE-derived threat list, risk scores, and a mitigation for each high/critical threat. You'll likely find real gaps to fix.
Show an example (the L7 server toolkit)
Tool: servertool.py (SSH-based remote admin)
Boundaries: my laptop → [SSH/TLS] → remote server
CLI args (untrusted?) → command construction
THREATS (ranked):
25 [T] command injection via an unsanitised arg → use list-form,
never f-string into a shell (L8-43)
20 [I] SSH key or creds leaked in logs/code → keys in env, never
log secrets (L8-44)
16 [E] tool runs as root, a bug = full compromise → least privilege
15 [S] connecting to a spoofed host → verify host keys (RejectPolicy)
9 [R] no record of who ran what → audit log every action (L8-45)
Fixes already in place: list-form commands, host-key checking,
secrets from env. Gap found: actions weren't audit-logged — adding it.Non-negotiables: a data flow with boundaries, STRIDE-derived threats, risk ranking, a mitigation per high/critical, and at least one real gap found.
Recap
3 minThreat modelling is structured paranoia, driven by four questions — what are we building, what can go wrong, what will we do, did we do well? Use STRIDE (Spoofing, Tampering, Repudiation, Information disclosure, Denial of service, Elevation of privilege) to surface threats systematically; map your trust boundaries (validate everything that crosses them) and minimise your attack surface. Rank threats by likelihood × impact and pair each with a mitigation. The output is a prioritised plan — and for this course, it's essentially the map of every lesson ahead.
Vocabulary Card
- STRIDE
- Six threat categories: Spoofing, Tampering, Repudiation, Info disclosure, DoS, Elevation.
- trust boundary
- A line where data enters a more-trusted zone and must be validated.
- attack surface
- All the points an attacker can interact with; minimise it.
- risk
- Likelihood × impact — used to prioritise which threats to fix first.
Homework
4 minThreat-model a complete small system of your choosing (a to-do web app, a chat app, an online shop checkout). Produce: a data-flow sketch with trust boundaries, a STRIDE threat table, risk scores, and a ranked mitigation plan. Keep it — you'll be able to map most mitigations to lessons in this level as you go.
Sample · threat model of a to-do web app
Flow: Browser →[TLS]→ Flask API →[boundary]→ SQLite
▲ validate task text, user id
Ranked threats:
25 [E] user A reads/edits user B's tasks (IDOR) → check ownership
on every query (broken access control, L8-28)
20 [T] SQL injection via task text → parameterised queries (L8-31)
16 [S] session hijack over http → enforce TLS + secure cookies
12 [I] stored XSS: task text rendered as HTML → escape output (L8-33)
9 [S] weak/no auth → hashed passwords + sessions (L8-12,37)
6 [D] giant task list → pagination + size limits
Plan: fix access control + SQLi first (the 25 and 20), then TLS
and XSS. Each maps to a Level 8 lesson — this model IS my syllabus.Non-negotiables: boundaries marked, STRIDE coverage, risk ranking, and mitigations that map to real fixes.