Threat Modeling for Developers#
Threat modeling is the practice of systematically identifying what can go wrong in a system before it goes wrong. It is not a security team activity that happens once. It is a design activity that happens every time the architecture changes.
The output of threat modeling is not a report that sits in a wiki. It is a prioritized list of threats that becomes security requirements in the backlog.
When to Threat Model#
- Before building a new service or feature. You are making design decisions. Threat modeling informs those decisions.
- When adding a new external integration. Every external system is a trust boundary.
- When changing authentication or authorization. These are high-impact areas where mistakes have outsized consequences.
- When handling new categories of data. PII, financial data, health data, credentials — each has different risk profiles and regulatory requirements.
- During architecture reviews. If the architecture is changing, the threat landscape is changing.
You do not need to threat model every bug fix or CSS change.
Step 1: Define What You Are Modeling#
Scope the exercise. Threat modeling the entire company is useless. Threat modeling “the payment processing flow” is actionable.
Start with:
- What does the system do? One paragraph.
- Who are the users? Human users, API clients, agents, internal services.
- What data does it handle? Classify by sensitivity: public, internal, confidential, restricted.
- What are the deployment boundaries? Where does it run? What infrastructure does it depend on?
Example:
System: Order Processing Service
Function: Accepts orders from the web frontend, validates payment via
Stripe API, stores order in PostgreSQL, sends confirmation
via email service.
Users: Authenticated customers, admin dashboard users, internal
fulfillment service.
Data: Customer PII (name, email, address), payment tokens (not full
card numbers), order history.
Deployment: Kubernetes cluster, AWS us-east-1, accessed via ALB.Step 2: Draw the Data Flow Diagram#
A data flow diagram (DFD) maps how data moves through the system. It has four elements:
- External entities — Users, browsers, external APIs, third-party services. Represented as rectangles.
- Processes — Your code, services, functions. Represented as circles.
- Data stores — Databases, caches, file systems, message queues. Represented as parallel lines.
- Data flows — Arrows showing data movement between elements. Label with what data flows and the protocol.
┌──────────┐ HTTPS/JSON ┌─────────────┐ gRPC/mTLS ┌──────────────┐
│ Customer │──────────────────→│ API Gateway │────────────────→│ Order Service │
│ (browser) │ │ │ │ │
└──────────┘ └──────────────┘ └──────┬───────┘
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ PostgreSQL│ │ Stripe │ │ Email │
│ (orders, │ │ API │ │ Service │
│ PII) │ │ (external│ │ (internal│
└──────────┘ └──────────┘ └──────────┘Trust Boundaries#
Draw dashed lines where trust changes:
╔═══════════════════════════════════════════════════════════╗
║ Internet (untrusted) ║
║ ┌──────────┐ ║
║ │ Customer │ ║
║ └─────┬────┘ ║
╠════════╪══════════════════════════════════════════════════╣
║ DMZ │ ║
║ ▼ ║
║ ┌──────────┐ ║
║ │ API GW │ ║
║ └─────┬────┘ ║
╠════════╪══════════════════════════════════════════════════╣
║ Internal cluster ║
║ ▼ ║
║ ┌──────────┐ ┌──────────┐ ┌──────────┐ ║
║ │ Order Svc│───→│ Postgres │ │ Email Svc│ ║
║ └─────┬────┘ └──────────┘ └──────────┘ ║
╠════════╪══════════════════════════════════════════════════╣
║ External APIs ║
║ ▼ ║
║ ┌──────────┐ ║
║ │ Stripe │ ║
║ └──────────┘ ║
╚═══════════════════════════════════════════════════════════╝Every trust boundary crossing is a place where threats concentrate. Data crossing a boundary needs authentication, authorization, encryption, and validation.
Step 3: Identify Threats with STRIDE#
STRIDE is a mnemonic for six categories of threats. Walk through each data flow and ask: “Can this be exploited through any of these categories?”
| Category | Question | Example |
|---|---|---|
| Spoofing | Can someone pretend to be someone else? | Forged auth token, stolen API key, DNS spoofing |
| Tampering | Can someone modify data in transit or at rest? | Man-in-the-middle on unencrypted traffic, SQL injection modifying records |
| Repudiation | Can someone deny they performed an action? | Missing audit logs, no transaction receipts |
| Information Disclosure | Can someone access data they should not? | Database query returns extra fields, error messages leak internals, logs contain PII |
| Denial of Service | Can someone make the system unavailable? | Unbounded query, no rate limiting, resource exhaustion |
| Elevation of Privilege | Can someone gain permissions they should not have? | BOLA (accessing other users’ data), mass assignment, JWT algorithm confusion |
Applying STRIDE to the Order Service Example#
Walk each data flow across each trust boundary:
Customer → API Gateway (Internet → DMZ)
| Threat | Category | Description |
|---|---|---|
| T1 | Spoofing | Attacker uses stolen session cookie to place orders as another customer |
| T2 | Tampering | Attacker modifies order total in the request body |
| T3 | DoS | Attacker floods the order endpoint with requests |
| T4 | Elevation | Attacker changes user ID in the request to access another customer’s account |
API Gateway → Order Service (DMZ → Internal)
| Threat | Category | Description |
|---|---|---|
| T5 | Spoofing | Compromised gateway sends requests with forged identity headers |
| T6 | Information Disclosure | Gateway logs contain full request bodies with customer PII |
| T7 | Tampering | If traffic is not encrypted, a network-level attacker can modify requests |
Order Service → PostgreSQL (Internal → Data Store)
| Threat | Category | Description |
|---|---|---|
| T8 | Tampering | SQL injection allows attacker to modify or delete order records |
| T9 | Information Disclosure | Over-broad database credentials allow Order Service to read tables it does not need |
| T10 | Repudiation | No audit trail for who modified order records |
Order Service → Stripe API (Internal → External)
| Threat | Category | Description |
|---|---|---|
| T11 | Spoofing | Stripe webhook callback is not verified — attacker sends fake payment confirmations |
| T12 | Information Disclosure | Stripe API key logged in error messages or debug output |
| T13 | DoS | Stripe rate limits our requests, causing order failures |
Step 4: Assess Risk#
Not all threats are equal. Score each threat to prioritize mitigation.
DREAD Scoring#
Rate each factor 1-3 (low-medium-high):
| Factor | Question |
|---|---|
| Damage | How bad is the impact if exploited? |
| Reproducibility | How easy is it to reproduce? |
| Exploitability | How much skill/effort to exploit? |
| Affected users | How many users are impacted? |
| Discoverability | How easy is it to find the vulnerability? |
Example scoring:
| Threat | D | R | E | A | D | Total | Priority |
|---|---|---|---|---|---|---|---|
| T1: Stolen session | 3 | 2 | 2 | 1 | 2 | 10 | High |
| T2: Modified order total | 3 | 3 | 2 | 1 | 2 | 11 | High |
| T4: BOLA via user ID | 3 | 3 | 3 | 3 | 3 | 15 | Critical |
| T8: SQL injection | 3 | 3 | 2 | 3 | 2 | 13 | Critical |
| T11: Fake webhook | 3 | 2 | 2 | 3 | 2 | 12 | High |
| T6: PII in logs | 2 | 3 | 1 | 3 | 1 | 10 | High |
| T3: Order endpoint DoS | 2 | 3 | 3 | 3 | 3 | 14 | Critical |
| T10: No audit trail | 1 | 3 | 1 | 1 | 1 | 7 | Medium |
Risk = Likelihood x Impact#
A simpler alternative to DREAD for fast triage:
│ Low Impact │ Med Impact │ High Impact
───────────┼────────────┼────────────┼────────────
High Likl. │ Medium │ High │ Critical
Med Likl. │ Low │ Medium │ High
Low Likl. │ Accept │ Low │ MediumStep 5: Define Mitigations#
For each high-priority threat, define a concrete mitigation that becomes a backlog item:
| Threat | Mitigation | Implementation |
|---|---|---|
| T4: BOLA | Derive user ID from JWT, never from request path | Code change in order handler |
| T8: SQL injection | Use parameterized queries everywhere | Code review + static analysis |
| T3: DoS | Rate limit order endpoint to 10/min per user | API gateway configuration |
| T2: Modified total | Recalculate total server-side, ignore client-provided total | Code change in order validation |
| T11: Fake webhook | Verify Stripe webhook signature on every callback | Code change in webhook handler |
| T1: Stolen session | Short session TTL (15 min), re-auth for payment | Auth configuration change |
| T6: PII in logs | Redact PII fields in logging middleware | Middleware change |
Each mitigation is specific, implementable, and testable. “Improve security” is not a mitigation. “Verify Stripe webhook signatures using stripe.webhooks.constructEvent()” is.
Step 6: Document and Maintain#
A threat model is a living document. Store it alongside the code:
docs/
threat-models/
order-service.md
payment-integration.md
user-authentication.mdEach document should contain:
- System description and scope
- Data flow diagram
- Trust boundaries
- Threat table (STRIDE analysis)
- Risk scores
- Mitigations with status (implemented / planned / accepted risk)
Review threat models when:
- The architecture changes.
- New data types are introduced.
- New integrations are added.
- A security incident reveals a missed threat.
Lightweight Threat Modeling in Practice#
Full STRIDE analysis on every feature is not realistic. Use a lightweight approach for day-to-day development:
Four Questions (Adam Shostack’s Framework)#
For every design document or PR that changes architecture:
- What are we building? (DFD or even a paragraph)
- What can go wrong? (brainstorm threats)
- What are we going to do about it? (mitigations)
- Did we do a good enough job? (review)
PR Security Checklist#
Add to your PR template:
## Security Considerations
- [ ] Does this change handle user input? If yes, is it validated?
- [ ] Does this change cross a trust boundary? If yes, is it authenticated and authorized?
- [ ] Does this change handle sensitive data? If yes, is it encrypted and not logged?
- [ ] Does this change introduce a new external dependency? If yes, is it pinned and verified?
- [ ] Could this change be abused at scale? If yes, are there rate limits?Most PRs check “no” on all items in seconds. The ones that check “yes” trigger a focused threat discussion.
Common Mistakes#
- Threat modeling after the system is built. By then, architectural decisions are locked in. Threat model during design, when changes are cheap.
- Listing threats without mitigations. A threat list without mitigations is anxiety without action. Every threat above the risk threshold needs a concrete mitigation plan with an owner.
- Scoring all threats as “critical” to be safe. If everything is critical, nothing is. Honest scoring lets you prioritize. Some risks are genuinely low and can be accepted.
- Not involving developers. Security teams doing threat modeling in isolation produce documents nobody reads. Developers know the system best and should lead the threat modeling with security guidance.
- Treating threat models as one-time documents. A threat model from last year does not cover this year’s integrations. Keep threat models alive alongside the code they describe.