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        │ Medium

Step 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.md

Each document should contain:

  1. System description and scope
  2. Data flow diagram
  3. Trust boundaries
  4. Threat table (STRIDE analysis)
  5. Risk scores
  6. 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:

  1. What are we building? (DFD or even a paragraph)
  2. What can go wrong? (brainstorm threats)
  3. What are we going to do about it? (mitigations)
  4. 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#

  1. Threat modeling after the system is built. By then, architectural decisions are locked in. Threat model during design, when changes are cheap.
  2. 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.
  3. 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.
  4. 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.
  5. 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.