Threat Modeling — End the Unsystematic Threat Hunt#
"The attacker is already threat-modeling you. If you don't do it too, the gap between you is their market advantage."
Threat modeling = keeping a record of what a system looks like, how it can be attacked, and which control mitigates which threat. Not a week-long formal exercise — a living document you can start in 90 minutes and attach to a PR.
🎯 Why Do It?#
| Problem | Threat modeling answer |
|---|---|
| "Is this new feature secure?" | Threat list + mitigation path for each threat |
| "Which control should we add?" | ROI list: high-threat / low-cost controls |
| "What should we look for in the pen-test?" | Threat model output = pentest scope |
| "The audit asks 'do you have a risk assessment'" | Threat model = the basis of the risk register |
| "A new engineer wants to learn system security" | It becomes an onboarding doc |
📐 Frameworks#
1. STRIDE (Microsoft, 2002 — still the most widely used)#
Evaluate each threat type one by one:
| Letter | Threat | Countermeasure category |
|---|---|---|
| Spoofing | Impersonating someone's identity | Authentication |
| Tampering | Modifying data | Integrity (signing, hash) |
| Repudiation | Being able to say "I didn't do it" | Audit log, non-repudiation |
| Information Disclosure | Unauthorized data reading | Encryption, RBAC |
| Denial of Service | Blocking access | Rate limit, quota, scaling |
| Elevation of Privilege | Escalating one's privilege | Least privilege, RBAC |
2. LINDDUN (Privacy-focused, for GDPR/KVKK compliance)#
| Letter | Threat |
|---|---|
| Linkability | Two separate records get linked to the same person |
| Identifiability | Data assumed anonymous is tied to a person |
| Non-repudiation | The person cannot deny (in a privacy context this is bad) |
| Detectability | It can be detected that a person is in the system |
| Disclosure | Information leakage |
| Unawareness | The user is unaware of data collection |
| Non-compliance | Legal non-compliance |
3. PASTA (Process for Attack Simulation and Threat Analysis)#
7-stage, tied to business risk, heavier. Good for regulation/finance.
4. Attack Trees#
Root = attacker goal, branches = ways to reach that goal.
Root: Steal customer card numbers
├── SQLi into the app
│ ├── No input validation
│ └── No prepared statement
├── Direct DB access
│ ├── DB exposed to the internet
│ ├── Weak DB password
│ └── Compromised admin laptop
└── Read from backup
├── Backup S3 public
└── Backup unencrypted
🔑 Practical advice: For most teams STRIDE is enough. For privacy/regulation-heavy work, STRIDE + LINDDUN. Attack tree → can also be done separately for critical features.
📐 Lightweight Version: The 90-Minute Threat Model#
When a new service design lands in a PR:
0–10 min: System diagram#
Draw a Data Flow Diagram (DFD). Mermaid is enough:
flowchart LR
User[(Browser)] -->|HTTPS| LB[Load Balancer]
LB -->|HTTPS| API[API Service]
API -->|TLS| DB[(PostgreSQL)]
API -->|HTTPS| Vault[Vault]
API -->|HTTPS| ExtSvc[3rd-party Payment]
Worker[Background Worker] -->|TLS| DB
Worker -->|HTTPS| Queue[(SQS)] 10–20 min: Draw the trust boundaries#
[INTERNET] ←→ [DMZ: LB, WAF] ←→ [APP TIER] ←→ [DATA TIER]
↓
[3RD PARTY: Payment] (external trust boundary)
Trust boundary = "at this point data crosses from the untrusted side to the trusted side". A control is written for each boundary.
20–60 min: STRIDE for each component#
| Component | S | T | R | I | D | E |
|---|---|---|---|---|---|---|
| LB | TLS termination, mTLS upstream | WAF rule | LB access log | TLS 1.3 | Rate limit | — |
| API | OAuth2/OIDC | Body schema validation | Audit log | Encryption-at-rest, RBAC | Rate limit + quota | RBAC, k8s RBAC |
| DB | DB user per service | FK constraint, transaction | DB audit (pgaudit) | TDE, column-level enc | Connection pool | Least-privilege role |
| External Payment | mTLS + signed JWT | Webhook signature verify | Webhook log | TLS only | Circuit breaker | API key in Vault |
| Background Worker | Service identity (SPIFFE) | Idempotency key | Job log | Same as API | Queue back-pressure | Worker SA |
60–80 min: Risk score + priority#
For each threat: - Likelihood: Low / Medium / High - Impact: Low / Medium / High - Risk = L × I (a simple 3x3 matrix is enough)
| Threat | L | I | Risk | Mitigation | Status |
|---|---|---|---|---|---|
| SQLi into the API | M | H | High | Prepared statements + input validation | ✅ Implemented |
| Vault token in pod env | M | H | High | File mount + tmpfs | ⚠️ TODO #234 |
| Webhook replay | L | M | Med | Nonce + timestamp + signature | ✅ Implemented |
| 3rd-party payment outage | M | M | Med | Circuit breaker + queue | ⚠️ TODO #235 |
80–90 min: Action items + owner#
Every unmitigated threat → JIRA/Linear ticket + owner + due date.
📝 Threat Model Template#
# Threat Model: <SERVICE_NAME>
**Date:** 2026-05-04
**Authors:** @halil, @security-team
**Status:** Draft / Review / Accepted
## 1. System Overview
<2-3 sentences: what it does, who uses it>
## 2. Data Flow Diagram
[Mermaid or draw.io]
## 3. Assets (to be protected)
| Asset | Sensitivity | Notes |
|---|---|---|
| Customer PII | High | Within KVKK scope |
| Card numbers | Critical | PCI DSS — not stored, tokenized |
| API keys | High | In Vault |
## 4. Trust Boundaries
- Internet ↔ DMZ (WAF, rate limit)
- DMZ ↔ App tier (mTLS)
- App ↔ DB (network policy + DB auth)
- App ↔ 3rd party (mTLS + signed JWT)
## 5. Threat Analysis (STRIDE)
[Component x STRIDE matrix as above]
## 6. Risk Register
[Likelihood × Impact table]
## 7. Mitigation Plan
| Threat | Mitigation | Owner | Due | Status |
|---|---|---|---|---|
## 8. Out of Scope
- DDoS at L3/L4 (at the CloudFlare gateway layer)
- Insider with full admin access (compensating: audit + vault)
## 9. Assumptions
- Cluster K8s 1.30+ and PSS restricted enforce
- All images signed with cosign
- mTLS everywhere via service mesh
## 10. References
- [Kubernetes-Hardening.md](Kubernetes-Hardening.md)
- [Secrets-Management.md](Secrets-Management.md)
🎯 How Often?#
| Trigger | Threat model |
|---|---|
| New service design (PR) | Full STRIDE (90 min) |
| New feature, existing service | Mini delta (30 min, only the changed part) |
| 3rd party integration | Full (new trust boundary) |
| Major architecture revision | Full redo |
| After a major incident | Together with the postmortem (gap analysis) |
| Annually | Refresh every service's TM |
🧰 Tools#
| Tool | Type | Cost |
|---|---|---|
| OWASP Threat Dragon | Open source, web/desktop | Free |
| Microsoft Threat Modeling Tool | Windows desktop | Free |
| IriusRisk | Enterprise | Commercial |
| ThreatModeler | Enterprise | Commercial |
| PyTM | Python code-as-threat-model | Free |
| Mermaid + Markdown + GitHub | Plain text | Free |
🔑 Practical advice: Markdown + Mermaid + GitHub repo. Don't defer threat modeling in order to learn a tool. A dev friend who knows Mermaid is enough for you.
PyTM example (threat model as code)#
from pytm import TM, Server, Datastore, Dataflow, Boundary, Actor
tm = TM("API Service")
internet = Boundary("Internet")
internal = Boundary("Internal Network")
user = Actor("User")
user.inBoundary = internet
api = Server("API Service")
api.inBoundary = internal
db = Datastore("PostgreSQL")
db.inBoundary = internal
req = Dataflow(user, api, "API Request")
req.protocol = "HTTPS"
req.dstPort = 443
query = Dataflow(api, db, "DB Query")
query.protocol = "PostgreSQL"
query.dstPort = 5432
query.isEncrypted = True
tm.process() # generates the threat report
🚨 Quick Threat Checklist (For Code Review)#
Ask in 60 seconds during PR review:
Authentication#
- If a new endpoint was opened, is there an auth check?
- Is the OAuth scope/audience correct?
- Does token revocation work?
Authorization#
- Is RBAC defined for the new resource?
- Is cross-tenant access blocked?
- Are admin endpoints on separate RBAC?
Input#
- Is there schema validation (JSON Schema, Zod, Pydantic)?
- Is SQL parameterized (prepared statement)?
- File upload size + content-type check?
- XSS escape (is the template engine correct)?
Output#
- Is sensitive data kept out of the logs (token, password, PII)?
- Does the error message avoid returning a stack trace in prod?
- Is CORS correctly restricted?
Crypto#
- TLS 1.2+ enforced?
- No hardcoded key/secret?
- Hash: bcrypt/scrypt/argon2 (not MD5/SHA1)?
Storage#
- Encryption at rest (DB, S3)?
- Is there a PII deletion procedure (KVKK/GDPR right-to-erasure)?
- Backup encrypted?
Network#
- Is the new service defined in a NetworkPolicy?
- Should it have been public or internal?
- Is egress to the internet needed, and why?
📊 Anti-Pattern Table#
| Anti-pattern | Why it's bad | Right way |
|---|---|---|
| "Threat model is the security team's job" | The team writes code, you're the architect | TM authoring belongs to the developer, security does the review |
| One-off document, dusty on the shelf | Architecture changed, the TM is stale | Living document, update on every major change |
| 100-page formal STRIDE for every service | Excessive burden → nobody does it | Lightweight version 90 min, depth for critical services |
| There's a risk list but no mitigation | "Visible" security | Every threat → owner + due date |
| Making up a formula for the threat | Pseudo-scores are not credible | Likelihood × Impact 3x3 is enough |
| Leaving external services out of scope | The supply chain is the weakest link | 3rd party trust boundary, write mitigation |
| Doing the TM while already using the product | Architecture decisions can't be undone | Design phase, before code is written |
| No threat catalog, every TM from scratch | Recurring threats get missed | Internal threat catalog, record common patterns |
🇹🇷 Turkey-Specific: KVKK + LINDDUN#
Under KVKK, threat modeling specifically requires the LINDDUN categories:
| LINDDUN threat | KVKK violation |
|---|---|
| Linkability | Pseudonymized data → identifying a person (Article 28) |
| Identifiability | Aggregated data but re-identification (Article 4) |
| Disclosure | Unauthorized data sharing (Articles 8-9) |
| Non-compliance | DPIA not performed (Article 28) |
📌 Practical: If a new feature processes personal data, do both STRIDE + LINDDUN together. See
19-Compliance/KVKK-Practical.md(Phase 4).
📋 Threat Modeling Checklist#
[ ] Every new service has a TM doc (mandatory field in the PR template)
[ ] TM lives in Git, in the same repo as the code (it stays alive)
[ ] Mermaid DFD + trust boundaries are clear
[ ] STRIDE tabulated for every component
[ ] Risk register: L × I matrix
[ ] Mitigation: owner + due date + status
[ ] Out-of-scope explicitly stated
[ ] Assumptions documented (security context, mTLS, etc.)
[ ] PR review checks for changes that require a TM update
[ ] Annual refresh of all critical services' TMs
[ ] Security team review signed off (sign-off)
[ ] Threat catalog: internal library of recurring patterns
[ ] If PII is involved, LINDDUN was also done
📚 References#
- Threat Modeling: Designing for Security — Adam Shostack (book, the best starting point)
- OWASP Threat Modeling Cheat Sheet — cheatsheetseries.owasp.org
- Microsoft STRIDE — learn.microsoft.com
- LINDDUN — linddun.org
- Threat Dragon — owasp.org/www-project-threat-dragon
- PyTM — github.com/izar/pytm
Kubernetes-Hardening.md— control catalog19-Compliance/KVKK-Practical.md(Phase 4)
"Threat modeling isn't 'being paranoid' — it's disciplined paranoia. You systematically know which threat you overlooked; and with the same system you also know what isn't worth worrying about."
🎓 Learning Path: This document is used as a "Read first" resource in the
F2module.