| title | Threat Modeling | |||||
|---|---|---|---|---|---|---|
| slug | threat-modeling | |||||
| category | appsec | |||||
| depth | 2 | |||||
| audit_level |
|
|||||
| last_reviewed | 2026-04-21 | |||||
| sources |
|
|||||
| triggers_strong |
|
|||||
| triggers_weak |
|
|||||
| related |
|
Last reviewed: 2026-04-14 | Next review: 2026-10-14 | Priority: Essential | Audit Level: 2-4 | Automation: None
A practical threat modeling reference. Covers STRIDE, LINDDUN (privacy), PASTA (risk), Data Flow Diagrams, trust boundaries, abuse cases, a reusable threat library, and a sample 90-minute workshop agenda.
If you remember nothing else: threat modeling is asking "what can go wrong here, who could make it go wrong, and what would we do about it" before you build.
| Trigger | Depth |
|---|---|
| New product or major feature | Full workshop (90 min) |
| New external entry point (API, webhook, file upload, agent tool) | Lightweight DFD review (30 min) |
| New trust boundary (multi-tenant, new identity provider) | Full workshop |
| Refactor crossing existing boundaries | Diff review |
| Vulnerability discovered | Update model, look for siblings |
Threat modeling is not a compliance artifact. If your model lives in a PDF nobody opens, you are doing it wrong. Keep it in the repo as a living markdown file next to the code it describes.
Adam Shostack reduced threat modeling to four questions. Use these as the agenda for any session:
- What are we working on? (Diagram)
- What can go wrong? (Threats)
- What are we going to do about it? (Mitigations)
- Did we do a good job? (Validation, retro)
Everything else is detail.
Pick one based on goal. Do not pick "all of them".
| Methodology | Best for | Output |
|---|---|---|
| STRIDE | Product features, technical threats | Per-element threat list |
| LINDDUN | Privacy reviews, GDPR/CCPA-relevant systems | Per-element privacy threats |
| PASTA | Business-critical apps, risk quantification | Risk-ranked attack scenarios |
| Attack Trees | Specific high-stakes asset (signing key, root account) | Tree of attack paths |
| Kill Chain / MITRE ATT&CK | Detection engineering, red team alignment | Coverage matrix |
| Quick / "rapid risk" | Sprint-scoped features | Single-page summary |
Most teams should default to STRIDE for tech, LINDDUN when PII is involved, and switch to PASTA for revenue-critical or regulated systems.
| Letter | Threat | Property violated | Example |
|---|---|---|---|
| S | Spoofing | Authenticity | JWT alg: none, IP allowlist bypass |
| T | Tampering | Integrity | Modifying webhook body in transit, prototype pollution |
| R | Repudiation | Non-repudiation | No audit log, shared admin accounts |
| I | Information disclosure | Confidentiality | Stack traces in 500s, IDOR, log PII |
| D | Denial of service | Availability | Unbounded recursion, no rate limit, ZIP bomb |
| E | Elevation of privilege | Authorization | RCE, missing authz check, sudo via crafted input |
Apply STRIDE to every element type as follows:
| Element | S | T | R | I | D | E |
|---|---|---|---|---|---|---|
| External entity | Y | Y | ||||
| Process | Y | Y | Y | Y | Y | Y |
| Data flow | Y | Y | Y | |||
| Data store | Y | Y | Y | Y |
Use this matrix to drive a structured walk: for every box marked Y, ask "how could this happen here?".
| Letter | Threat |
|---|---|
| L | Linkability (correlate two records to the same person) |
| I | Identifiability (re-identify a pseudonymous record) |
| N | Non-repudiation (forced accountability for the user) |
| D | Detectability (an attacker can detect a record exists) |
| D | Disclosure of information |
| U | Unawareness (user has no idea what is collected) |
| N | Non-compliance (GDPR / CCPA / sector law) |
Pair with privacy-data-minimization.md for mitigations.
Seven stages, business-driven:
- Define business objectives
- Define technical scope
- Application decomposition (DFD + components)
- Threat analysis (intel-driven: who would attack you)
- Vulnerability and weakness analysis (combine with SAST/SCA findings)
- Attack modeling (build trees, simulate)
- Risk and impact analysis (quantify, prioritize, accept or fix)
PASTA produces ranked attack scenarios with business impact, which is what you need to defend a remediation budget to leadership.
The single most useful artifact. Five symbols, that is it:
| Symbol | Meaning |
|---|---|
| Square / rectangle | External entity (user, third-party API, identity provider) |
| Circle | Process (your code) |
| Two parallel lines | Data store (DB, S3 bucket, KV cache) |
| Arrow | Data flow |
| Dashed line | Trust boundary (this is where threats are born) |
┌──────────┐ HTTPS POST /upload ╔═════════╗ put ┌─────────┐
│ Browser │ ────────────────────► ║ API ║ ────────────► │ S3 │
└──────────┘ (cookie auth) ║ process ║ └─────────┘
╚═════════╝
│ AV scan req
▼
┌─────────┐
│ ClamAV │
└─────────┘
└──── trust boundary: internet ─────┘└── trust boundary: VPC ──┘
Once you have the diagram, every arrow that crosses a dashed line is a place to ask STRIDE questions. That is 80% of threat modeling done.
- draw.io / diagrams.net - free, runs in the browser, exports
.drawio.svgyou can commit to git. - OWASP Threat Dragon - DFD + STRIDE, web and desktop.
- pytm / threagile - threat modeling as code, integrates with CI.
- Microsoft Threat Modeling Tool - Windows-only, generates STRIDE threats automatically.
A trust boundary exists wherever the level of trust changes. Common ones:
- Internet -> your edge (CDN, WAF, ALB)
- Edge -> application
- Application -> database
- Application -> third-party API
- Tenant A -> tenant B (multi-tenant SaaS)
- User space -> kernel
- Frontend (untrusted runtime) -> backend
- LLM agent -> tool execution layer (see
mcp-security.md) - Production -> non-production
- Service mesh sidecar boundaries (mTLS terminations)
For each boundary: what authenticates? What authorizes? What logs? What rate-limits?
For every user story, write the matching abuse story:
| User story | Abuse story |
|---|---|
| As a user, I can reset my password via email | As an attacker, I can take over an account by hijacking the reset link |
| As a user, I can upload a profile picture | As an attacker, I can upload a polyglot file that runs JS or executes server-side |
| As a user, I can invite teammates by email | As a spammer, I can use the invite endpoint to send phishing |
| As an admin, I can impersonate a user for support | As a malicious admin, I can read PII without an audit trail |
| As a user, I can ask the agent to summarize my docs | As an attacker, I can hide instructions in a doc to make the agent exfiltrate data |
Add abuse stories to the same backlog as user stories, with the same "definition of done" for security tests.
Pull from this list before starting any session - it covers 80% of what you will find.
- Credential stuffing on /login (no rate limit, no captcha after N failures)
- Account enumeration via differential responses on /login or /forgot
- Password reset token guessable / unbounded validity / reusable
- JWT
alg: none,algconfusion, signing key in repo - OAuth
statemissing -> CSRF on link account - Session fixation, session not rotated on privilege change
- MFA bypass via "remember device" cookie with no binding
- IDOR via
?id=,/users/123 - BFLA: regular user calls admin endpoint that exists but is not linked
- Mass assignment: setting
is_adminin a profile update - Tenant cross-talk in shared tables (no
WHERE tenant_idenforced) - Frontend-only role check (server trusts the JWT claim without re-verifying)
- Backups public or readable by anyone in the cloud account
- Logs contain tokens / PII / passwords
- Third-party error tracker receives full stack with PII
- Database snapshot shared with a wrong account ID
- Passwords hashed with SHA-256 (see
applied-cryptography.md) - Hand-rolled crypto, ECB mode, static IV
- TLS 1.0/1.1 still enabled, weak ciphers
- Signing key in environment variable, never rotated
- Direct dep with known CVE, no policy to patch
- Transitive dep with known CVE
- Typo-squatted package, dependency confusion
- CI uses
actions/checkout@main(mutable ref)
- Indirect prompt injection via fetched content
- Tool with too-broad scope (
exec_shellinstead ofgit_commit) - Confused deputy: agent runs as a powerful user, takes a low-trust user's instructions
- Output of one tool is fed to another without sanitization
- Single point of failure with no failover
- No incident runbook (see
incident-playbooks.md) - No way to revoke a compromised user session at scale
- No detection on the high-value action you are most worried about
Pre-work for the team (must be done the day before, not in the room):
- Read the design doc.
- Skim this file and
owasp-top10.md. - Bring laptops.
| Time | Activity |
|---|---|
| 0:00 - 0:10 | Scope and goals. What feature, what is in/out, who is the attacker we care about. |
| 0:10 - 0:25 | Draw the DFD on a whiteboard / Excalidraw / draw.io. Mark trust boundaries. |
| 0:25 - 0:35 | List assets and what "compromise" means for each (CIA + privacy). |
| 0:35 - 1:05 | STRIDE walk per element. Timebox 30 minutes. One scribe captures threats in a markdown table. |
| 1:05 - 1:20 | Triage: for each threat, decide mitigate / accept / transfer / eliminate. Owner + due date. |
| 1:20 - 1:30 | Wrap-up. Confirm follow-up tickets exist. Schedule a 15-minute retro after the feature ships. |
Output (commit this in the repo):
# Threat model: <feature>
- Date:
- Facilitator:
- Participants:
## DFD

## Assumptions
- ...
## Threats
| ID | Element | STRIDE | Threat | Likelihood | Impact | Mitigation | Owner | Status |
|----|---------|--------|--------|------------|--------|------------|-------|--------|
| 01 | /upload process | T | Polyglot SVG XSS | Med | High | DOMPurify on render, CSP | @alice | Open |
| 02 | S3 store | I | Bucket public | Low | Critical | Block public access, CIS scan | @bob | Done |
## Decisions and accepted risks
- ...- Boil the ocean - trying to model the whole system. Always scope to a feature.
- No diagram - skipping the DFD because "we all know how it works". You do not.
- No follow-through - threats become tickets or the session was theater.
- One person modeling alone - threat modeling is a team activity. Different roles surface different threats.
- Treat it as a phase gate - it should be continuous, not a one-time signoff.
- Use jargon to look smart - if a junior dev cannot follow the session, you are doing it wrong.
- Skip the retro - go back after the feature ships and check which threats actually materialized. That is how you calibrate.
| Threat surfaced | Where to find mitigations |
|---|---|
| Injection of any kind | language-patterns.md, database-security.md |
| Auth / session | authorization-rbac.md, framework-examples.md |
| Crypto | applied-cryptography.md |
| Supply chain | supply-chain-security.md |
| Cloud misconfig | cloud-iam-hardening.md, container-k8s-hardening.md |
| LLM / agent | llm-agent-security.md, mcp-security.md |
| Privacy (LINDDUN) | privacy-data-minimization.md |
| Detection / response | incident-playbooks.md, vuln-management.md |