Chapter 52 — Threat modeling and secure design
In one sentence
Threat modeling is a structured conversation, held before you build, about what you're building, what can go wrong, and what you'll do about it — and it's the cheapest security activity per bug prevented.
The four questions (Shostack's framework)
1. WHAT ARE WE BUILDING? a diagram: components, data stores, data
flows, and TRUST BOUNDARIES (where data
crosses from less- to more-trusted, or
between owners). Keep it simple -- a
whiteboard DFD, not a 40-page doc.
2. WHAT CAN GO WRONG? enumerate threats against each element and
each flow crossing a boundary (use STRIDE
as a prompt).
3. WHAT ARE WE GOING TO DO? for each threat: MITIGATE (add a control),
ELIMINATE (remove the feature/data),
TRANSFER (someone else's problem -- e.g.
use a managed service), or ACCEPT
(document, with sign-off).
4. DID WE DO A GOOD JOB? review it; revisit when the design changes;
check the mitigations actually shipped and
have tests.
STRIDE — a checklist for "what can go wrong"
S Spoofing pretending to be someone/something
-> authentication (Ch 46), mTLS (Ch 31), signed messages (Ch 21)
T Tampering unauthorised modification of data/code
-> integrity: AEAD (Ch 15), HMAC/signatures, input validation,
access control (Ch 45), signed artifacts (Ch 47)
R Repudiation "I didn't do that" (and you can't prove otherwise)
-> audit logging (Ch 50), signatures/non-repudiation (Ch 21)
I Information disclosure exposure of data to the wrong party
-> encryption in transit/at rest, least privilege, redaction,
avoiding verbose errors, minimising data collected
D Denial of service making it unavailable
-> rate limiting, quotas, timeouts, autoscaling, input-size caps,
algorithmic-complexity review, CDN/anti-DDoS
E Elevation of privilege gaining capabilities you shouldn't have
-> authz on every request (Ch 45), sandboxing, least privilege,
memory-safe languages / careful native code, no injection (Ch 42)
Walk each component and data flow, ask "how could each letter apply
here?" -- STRIDE is a creativity prompt, not a compliance form.
Other lenses you can layer in: attack trees (goal → sub-goals), LINDDUN (privacy threats), abuse/misuse cases, kill-chain / MITRE ATT&CK (how a real adversary would progress), and "what would a malicious insider do?".
Secure-design principles (Saltzer & Schroeder, still current)
Least privilege minimum rights, minimum time
Fail-safe defaults deny by default; errors deny access (Ch 3:
fail-closed vs fail-open -- choose per context)
Defense in depth no single control is trusted to hold
Complete mediation check EVERY access, every time (no caching a
stale "yes" -- Ch 45)
Economy of mechanism keep the security-critical part small and
simple enough to audit
Open design security from keys/config, not secrecy of
design (Kerckhoffs, Ch 12)
Separation of privilege require two conditions (MFA, 4-eyes on
deploys, split keys)
Least common mechanism don't share state/resources across trust
levels (multi-tenant isolation)
Psychological acceptability if it's too painful, people bypass it ->
design the secure path to be the easy path
Minimise attack surface fewer endpoints, features, deps, ports, data
Secure the weakest link attackers target it; find yours
The SecureShop threat model (the Chapter 4 payoff)
Chapter 4 asked you to write down your instincts for four questions. Here's the worked answer, as a threat model. Compare it to what you wrote.
SYSTEM (DFD, simplified):
[Customer browser] --HTTPS--> (TB1) [edge/nginx] --> (TB2) [web/API]
|
(TB3) --mTLS-->[orders-api]
|
(TB4) -------->[PostgreSQL]
[Admin browser] --HTTPS+mTLS--> (TB1') [admin app] --(TB3)--> orders-api
[CI/CD] --signed artifact--> (TB5) [deploy] --> web/API, orders-api
TRUST BOUNDARIES: TB1 internet->DMZ, TB2 DMZ->app, TB3 service->service,
TB4 app->data, TB5 build->runtime, plus browser<->server (attacker owns
the client).
THREATS (STRIDE, selected) and DECISIONS:
PASSWORD STORAGE (your Ch 4 instinct: ______)
T/I: DB theft -> offline cracking.
-> Argon2id, per-user salt, pepper in KMS (Ch 10). MFA available;
passkeys roadmap (Ch 46). Breached-password screening. ACCEPT
residual risk of weak user passwords, mitigated by MFA + rate limit.
KEY STORAGE (your Ch 4 instinct: ______)
I/E: JWT signing key, DB creds, TLS private keys.
-> Secrets manager; workload identity; no secrets in repo/image
(Ch 49). 2-versions-live rotation runbook, tested. TLS keys
non-exportable where possible.
PER-CUSTOMER AUTHZ (your Ch 4 instinct: ______)
E/I: IDOR on /orders/:id, /baskets/:id; horizontal escalation.
-> Ownership in the WHERE clause on every query; centralised policy
layer; 404 not 403; automated "as another user" tests (Ch 45).
Object IDs are UUIDv4 (defence in depth, not the control).
PRICE / TOTAL TAMPERING (your Ch 4 instinct: ______)
T: client submits price/total/discount.
-> Server recomputes every monetary value from DB prices; coupons
validated server-side; ignore client-supplied totals entirely.
Idempotency keys + row locks on checkout to kill race conditions
(Ch 45, insecure design).
OTHER NOTABLE:
S admin impersonation -> admin app requires mTLS + SSO + MFA;
short sessions; admin actions logged &
alerted (Ch 50).
T supply-chain -> SBOM + scan + signed images verified at
deploy; Actions pinned to SHA (Ch 47).
R "I didn't refund that" -> immutable audit log of all admin/money
actions (Ch 50).
I SSRF from "import from URL"-> allowlist + post-resolution IP check;
IMDSv2; isolated egress (Ch 44).
D checkout/login flooding -> rate limits per-account & per-IP; WAF;
CDN; input-size caps.
E container breakout -> non-root, read-only FS, seccomp,
dropped caps, segmented network (Ch 39).
RESIDUAL RISKS (accepted, with owner + review date): weak user
passwords (mitigated), a rogue CA issuing for our domain (detected via
CT monitoring, Ch 30), a determined malicious insider with DB access
(mitigated by logging + separation of duties, not eliminated).
When and how to do it (lightweight, continuous)
* At design time for anything new or significantly changed. 60-90 min,
the team + a security-minded facilitator, a whiteboard.
* "Threat model every story" for high-risk components: a 2-line
"security notes" field in the ticket.
* Keep the artifact IN THE REPO (docs/threat-model.md + the diagram as
code -- e.g. `pytm`, Threagile, or just Mermaid). Review it in PRs
that change the design.
* Track mitigations as issues; verify they shipped with tests.
* Re-run after incidents (a real attack is ground truth).
Practice / capstone (90 min): threat-model your own project
1. Pick a real system you work on (or SecureShop).
2. Draw the DFD: components, data stores, flows, trust boundaries.
(Whiteboard photo, or Mermaid, or `pytm`.)
3. For each element and each flow crossing a boundary, run STRIDE.
Aim for ~15-30 concrete threats.
4. For each threat, decide: mitigate / eliminate / transfer / accept.
Name the control and where it lives in this guide.
5. List residual/accepted risks with an owner and a review date.
6. Cross-check against the secure-design principles list -- any
violated? (Shared DB creds across services? A cached authz "yes"?
A security check that's easy to skip?)
7. Produce docs/threat-model.md + diagram, and open issues for the
top 5 unmitigated threats.
8. Compare with your Chapter 4 notes: what did you get right by
instinct, and what did this guide change?
Common confusions
- "Threat modeling needs a specialist and weeks." A team with a whiteboard and the four questions produces most of the value in 90 minutes. Depth comes with practice.
- "STRIDE is a compliance checklist." It's a prompt for imagination against each component. The output is threats and decisions, not a filled form.
- "We did it once." Designs change; threat models are living docs, reviewed when architecture changes and after incidents.
- "We'll accept that risk" (verbally)." Accepted risk must be written, with an owner and a review date, and visible to someone accountable. Otherwise it's just an unfixed bug.
- "Secure design is about adding controls." Often the best move is elimination — don't collect the data, don't expose the endpoint, use a managed service — which removes the threat instead of guarding it.
Check yourself
- State Shostack's four questions.
- Expand STRIDE and give one mitigation for each letter.
- What is a trust boundary, and why do threats cluster at them?
- Name five Saltzer & Schroeder principles and what each means in one line.
- For SecureShop's "client submits the price" threat, what's the fix, and which STRIDE letter(s) is it?
- What are the four possible decisions for a given threat?
Further reading
- Book: Threat Modeling: Designing for Security, Adam Shostack — the
standard text. Also his free "Threat Modeling 101" resources and the Threat
Modeling Manifesto (
threatmodelingmanifesto.org). - Paper: Saltzer & Schroeder, "The Protection of Information in Computer Systems" (1975) — the design principles, still quoted 50 years on.
- Tools: Microsoft Threat Modeling Tool; OWASP
pytm(threat model as code); Threagile; OWASP Threat Dragon (free, diagram-based). - Cheat sheet: OWASP "Threat Modeling Cheat Sheet" and "Secure Product Design Cheat Sheet".
- Privacy lens: LINDDUN (
linddun.org) for privacy threat modeling.
End of Part 11 — Milestone check
- I can list where secrets leak and order storage approaches best→worst
- I set up secret scanning + push protection and issued a dynamic, short-lived credential
- I can describe the 2-versions-live rotation pattern and have tested one
- I know what to log, what never to log, and why logs must be central and append-only
- I built a detection baseline: schema, rules with runbooks, simulated attacks, measured MTTD
- I can recite the IR lifecycle and know preparation is most of it
- I know the containment trade-offs and the order of volatility
- I ran an IR tabletop end to end and wrote it up
- I can run a STRIDE threat model against a real system
- I produced a threat model for my own project and compared it to my Chapter 4 instincts