A STRIDE threat model, worked on a real system
What it is
STRIDE is a checklist for finding threats by category, so you enumerate systematically rather than imagining attacks ad hoc. Each letter is a threat that is the violation of one security property:
| Threat | Violates | Is |
|---|---|---|
| Spoofing | Authenticity | Pretending to be someone or something else |
| Tampering | Integrity | Modifying data or code in transit or at rest |
| Repudiation | Non-repudiation | Denying an action with no proof it happened |
| Information disclosure | Confidentiality | Exposing data to someone not authorised |
| Denial of service | Availability | Making a system unavailable |
| Elevation of privilege | Authorisation | Gaining capabilities you should not have |
The method is mechanical and that is the point. You draw a data-flow diagram, and for every element (process, data store, data flow, external entity) you ask which of the six apply. Enumerating against a checklist finds the threats that imagination skips, particularly repudiation and denial of service, which almost never come up in unstructured brainstorming.
What it is confused with: a risk assessment. STRIDE finds threats; it does not rank them. A threat model is the input to a prioritisation, and the common failure is producing a list of forty threats with no decision about which three to fix. The output of a threat model is a short list of design changes, not a long list of possibilities.
The problem it solves
"Is this design secure?" is unanswerable and "does this design have a spoofing threat on the auth flow?" is answerable. STRIDE converts an open-ended question into an enumeration.
The specific gaps it closes, which unstructured security review consistently misses:
Repudiation. Almost nobody brainstorms "can a user deny they did this," and it is the threat that matters most in financial and compliance contexts. The fix (an append-only audit log written on a path the actor cannot alter) is cheap if designed in and expensive to retrofit.
Denial of service against a specific component. Teams consider DDoS at the edge and miss the expensive database query an unauthenticated endpoint permits, or the unbounded work a single request can request.
Trust boundaries. The most valuable output of the data-flow diagram is the trust boundaries, because every threat of interest crosses one. A data flow entirely within a trust boundary is usually not where the threats are; a flow crossing from the internet to your application, or from your application to a third party, is.
Mechanics
The data-flow diagram
┌─── TRUST BOUNDARY: internet ────┐
│ │
[User] ──HTTPS──▶ (API Gateway) ──▶ (Auth Service) ──▶ [Session Store]
│ │ │
│ ▼ │
│ (Order Service) ──▶ [Order DB]
│ │ │
└── TRUST BOUNDARY ┼── internal ───┘
▼
(Payment Service) ──▶ [Stripe]
│ (3rd party)
TRUST BOUNDARY: external vendor
Symbols: processes are circles, data stores are parallel lines, data flows are arrows, external entities are rectangles, and trust boundaries are the dashed lines the arrows cross. The diagram is the whole method: you cannot enumerate threats on a system you have not drawn.
Applying STRIDE per element
The mapping of which threats apply to which element type is itself a checklist:
Element type Applicable threats
──────────────────────────────────────────────
External entity Spoofing, Repudiation
Process ALL SIX
Data store Tampering, Info disclosure, Repudiation, DoS
Data flow Tampering, Info disclosure, DoS
Worked, for one data flow: User -> API Gateway (crossing the internet boundary):
S (Spoofing): Can an attacker impersonate a legitimate user?
Threat: stolen session token, credential stuffing, no MFA
Control: short-lived tokens, MFA, DPoP-bound tokens (see the OAuth page)
T (Tampering): Can the request be modified in transit?
Threat: MITM if TLS is misconfigured; parameter tampering
Control: TLS with HSTS; server-side validation of every parameter
I (Info disc): Can the response leak data to the wrong user?
Threat: IDOR (fetching /orders/4472 when you own 4471);
verbose error messages
Control: object-level authorisation on every request; generic errors
D (DoS): Can one client exhaust the service?
Threat: an expensive unauthenticated endpoint; no rate limit
Control: rate limiting; auth before expensive work; query cost limits
R and E do not apply to a data flow directly; they apply to the processes
at each end.
IDOR (Insecure Direct Object Reference) under the "I" is the highest-frequency real vulnerability, and STRIDE surfaces it every time because the question "can the response leak data to the wrong user" is asked of every flow that returns data.
Turning threats into a decision
A raw threat list is not a threat model. Each threat gets a disposition:
Threat -> one of:
MITIGATE add a control
ELIMINATE remove the feature or the data
TRANSFER push the risk elsewhere (a vendor, insurance, the user)
ACCEPT document that the risk is understood and tolerated
"Accept, documented" is a legitimate and underused disposition. Not every threat warrants a control, and a threat model that mitigates everything is either padding the list or over- engineering. The value is a decision per threat, and "we accept this because the impact is low and the cost of mitigation is high" is a decision.
Ranking, when you need it, is impact times likelihood, and DREAD is the classic scoring and is largely deprecated because its scores are subjective and not comparable across teams. The practical replacement is a simple impact-and-likelihood matrix, or tying each threat to a concrete abuse case.
The property that makes it repeatable
STRIDE composes with the architecture rather than replacing security review, and its value compounds because the data-flow diagram is a durable artifact. A new feature adds a flow, and you apply the six questions to that flow, rather than re-modelling the system. Threat modelling per change is far cheaper than threat modelling the whole system annually, and it is where the method earns its place.
A worked example: threat-modelling a payment refund flow
A team is adding self-service refunds to an e-commerce platform: a customer requests a refund, support approves it, and the money is returned via Stripe.
The data-flow diagram, drawn first:
┌── internet ──┐ ┌──────── internal ────────┐ ┌─ vendor ─┐
│ │ │ │ │ │
[Customer]─▶(Web App)─────▶(Refund Service)─┬──▶[Refund DB] │
│ │ │ │ │ │
[Support]──▶(Admin Panel)─▶(Refund Service) └──▶(Payment Svc)──▶[Stripe]
│ │ │ │
└──────────────┘ └───────┼── audit ──▶[Audit Log]
│
TRUST BOUNDARIES: internet | internal | vendor
Applying STRIDE to the Refund Service (a process, so all six apply):
S (Spoofing):
Threat: a customer forges an "approved" state, or calls the refund
endpoint directly, bypassing support approval.
Finding: the approval was a boolean the WEB APP sent. The customer
could set approved=true.
-> ELIMINATE: approval state lives server-side, set only by the admin
panel with a support role. The web app cannot set it.
T (Tampering):
Threat: the refund AMOUNT is modified between request and execution.
Finding: the amount came from the client request, not from the order.
A customer could refund more than they paid.
-> MITIGATE: the amount is computed server-side from the order total
minus prior refunds. The client cannot supply it.
R (Repudiation):
Threat: support denies approving a fraudulent refund; a customer
denies requesting one.
Finding: NO audit log existed. This is the threat nobody had raised.
-> MITIGATE: append-only audit log, written on the server path,
recording actor, action, amount, timestamp, and the approving
support agent's identity. Immutable and off the refund service's
own write path.
I (Information disclosure):
Threat: a customer views another customer's refunds (IDOR).
Finding: GET /refunds/{id} checked authentication but not ownership.
-> MITIGATE: object-level authorisation: the refund's customer_id
must match the authenticated user (or a support role).
D (Denial of service):
Threat: refund requests exhaust the Stripe rate limit or the DB.
Finding: no rate limit on the request endpoint; a script could file
thousands.
-> MITIGATE: rate limit per customer; a support queue rather than
immediate execution, so volume is bounded by approval capacity.
E (Elevation of privilege):
Threat: a low-privilege support agent approves refunds above their
limit, or a customer reaches admin functions.
Finding: any support agent could approve any amount.
-> MITIGATE: approval limits by role; refunds above a threshold need
a second approver (see the toxic-combination below).
The repudiation finding was the one that mattered most and had not been raised, because nobody brainstorms "can support deny they did this." The audit log it produced was the control a subsequent fraud investigation depended on.
The toxic combination, found by looking across threats:
The refund flow, combined with the earlier findings:
- a support agent can approve a refund
- the refund amount, once server-computed, is correct
- BUT a support agent could ALSO create a fake order (a separate
feature), and then refund it
-> a single support agent could create an order and refund it to an
attacker-controlled card, with no second party involved.
No single STRIDE cell caught this; looking across the model did. The mitigation was separation of duties: the agent who can create orders cannot approve refunds, and refunds above a threshold need a second approver. That is the class of finding that structured modelling produces and ad hoc review misses: a chain that is safe at each step and dangerous end to end.
The dispositions, as the actual deliverable:
Threat Disposition Cost
────────────────────────────────────────────────────────────
Client-set approval ELIMINATE medium (server state)
Client-set amount MITIGATE low (compute server-side)
No audit log MITIGATE medium (new store + writes)
IDOR on refund lookup MITIGATE low (ownership check)
Unbounded refund requests MITIGATE low (rate limit + queue)
Unlimited approval by any agent MITIGATE low (role limits)
Create-and-refund collusion MITIGATE medium (separation of duties)
Stripe webhook forgery MITIGATE low (verify webhook signature)
Refund to a different card ACCEPT (Stripe refunds to the
original payment method only;
documented, no control needed)
Nine threats, eight controls, one documented acceptance. The acceptance is as important as the mitigations: it records that someone considered "refund to a different card," established that Stripe's API makes it impossible, and decided no control was needed. Without it, a future reviewer re-discovers the threat and re-investigates.
The whole exercise took about three hours and its output was a nine-row table, not a document. That ratio is the point: the model is cheap and the artifact is a decision list.
Production evidence
STRIDE was developed at Microsoft (Loren Kohnfelder and Praerit Garg, 1999) and is the core of Microsoft's Security Development Lifecycle. The Microsoft Threat Modeling Tool implements the data-flow-diagram-plus-STRIDE method and is the reference implementation.
The Threat Modeling Manifesto (2020), authored by a group including Adam Shostack, states the four questions that frame any threat model ("what are we working on, what can go wrong, what are we going to do about it, did we do a good enough job") and is the current consensus framing. STRIDE answers the second question.
OWASP's Threat Modeling resources and pytm (a code-based threat modelling tool) reflect the shift toward threat models as artifacts kept alongside code, updated per change rather than produced annually, which is the per-change property that makes the method affordable.
DREAD's deprecation is documented by Microsoft itself, which moved away from it in favour of simpler impact-likelihood ranking because DREAD's numeric scores were not reproducible across assessors. That a framework's own authors deprecated its scoring component is worth knowing.
IDOR (Insecure Direct Object Reference) is consistently among the most common real vulnerabilities in bug bounty data, and it is precisely the "information disclosure on a data flow returning data" cell of STRIDE. The method surfaces the single most common vulnerability class every time it is applied.
The debate
Is threat modelling worth the time? For a new feature that crosses a trust boundary or handles sensitive data, yes, and the worked example is why: three hours produced eight design changes including the audit log a later fraud investigation depended on and the separation of duties that closed a collusion path. For a feature entirely within one trust boundary handling no sensitive data, it is usually not worth a formal model, and the judgement of when to model is itself part of the skill.
STRIDE or attack trees or something else? STRIDE for breadth (enumerate all categories against all elements) and attack trees for depth (how would an attacker achieve this specific goal). They are complementary: STRIDE finds the threats and an attack tree explores the serious one. For most feature-level modelling STRIDE alone is sufficient, and reaching for attack trees is warranted when a specific high-value target needs adversarial analysis.
Who should do it? The engineers building the feature, with a security reviewer, not a separate security team modelling a system they did not build. The value is partly the artifact and largely the conversation, because the person who wrote the code knows where the client-supplied amount came from, and a security team reviewing a diagram does not. Threat modelling done to a team produces a worse model than threat modelling done by it.
How do you keep it from becoming a forty-threat document nobody reads? By requiring a disposition per threat and treating the deliverable as the disposition table, not the threat list. A threat with no decision is noise, and "accept, documented" is a valid decision that keeps the list honest. The failure mode is a comprehensive model that changes no design, which is worse than no model because it consumed the time and produced nothing.
Is DREAD useful? No, and its own authors deprecated it. Its scores are subjective and not comparable across assessors, so a "6.4 DREAD" from one team means something different from another's. Impact times likelihood, or tying each threat to a concrete abuse case, is more honest than a false-precision number.
What is the single most valuable output? The trust boundaries on the diagram, because every threat of interest crosses one. A team that draws the diagram and marks the boundaries has done most of the work, because it now knows exactly which data flows to scrutinise: the ones leaving the browser, the ones reaching a third party, and the ones crossing from a lower-trust service to a higher-trust one.
Follow-up Q&A
"What does STRIDE stand for and what is each for?"
Spoofing (violating authenticity: impersonation), Tampering (integrity: modifying data or code), Repudiation (non-repudiation: denying an action with no proof), Information disclosure (confidentiality: exposing data), Denial of service (availability), and Elevation of privilege (authorisation: gaining capabilities you should not have). Each is the violation of one security property, and the method is to draw a data-flow diagram and ask which of the six apply to every element. Enumerating against the checklist finds the threats imagination skips, especially repudiation and component-level DoS.
"How do you actually do it?"
Draw the data-flow diagram with trust boundaries, because the threats of interest all cross a boundary. Then apply STRIDE per element, using the element-type-to-threat mapping: a process gets all six, a data flow gets tampering, disclosure and DoS. For each threat, assign a disposition: mitigate, eliminate, transfer, or accept-documented. The deliverable is that disposition table, not the threat list, and a three-hour model on a refund flow produced nine rows and eight design changes.
"What is the highest-value output?"
The trust boundaries, because every threat worth mitigating crosses one. Once the diagram marks where data leaves the browser, reaches a third party, or crosses from a lower-trust to a higher-trust service, you know exactly which flows to scrutinise. And repudiation, because nobody brainstorms "can this actor deny they did this," so the audit log it demands is almost always missing and is cheap to design in and expensive to retrofit.
"What is a threat STRIDE finds that ad hoc review misses?"
Repudiation and toxic combinations. In one refund model the missing audit log was the finding nobody had raised, and a later fraud investigation depended on it. And looking across the model found a chain safe at each step and dangerous end to end: a support agent who could create an order and also approve a refund could refund a fake order to an attacker's card, with no second party. No single STRIDE cell caught it; enumerating and then looking across did, and the fix was separation of duties.
"Should DREAD be used to rank threats?"
No, and Microsoft deprecated it. Its numeric scores are subjective and not comparable across assessors, so a DREAD number carries false precision. Impact times likelihood on a simple matrix, or tying each threat to a concrete abuse case, is more honest. The more important point is that the output of a threat model is a decision per threat, not a ranking, and "accept, documented" is a valid decision that keeps the list from becoming padding.
"When is threat modelling not worth it?"
For a feature entirely within one trust boundary that handles no sensitive data. The judgement of when to model is part of the skill, and modelling everything produces documents nobody reads. The trigger is crossing a trust boundary or handling sensitive data, and the deliverable should always be a short disposition table rather than a long threat list, or the exercise consumes time and changes no design.
Common misconceptions
"A threat model is a risk assessment." STRIDE finds threats; it does not rank them. The output is a disposition per threat, and a model that produces forty threats and no decisions has failed.
"You brainstorm attacks." The method is mechanical: a data-flow diagram, then six questions per element. The whole point is to find what brainstorming misses, particularly repudiation and component-level DoS.
"DREAD gives you a priority." It gives you a subjective number that does not compare across teams. Its own authors deprecated it.
"Threat modelling is a security-team activity." It is done best by the engineers building the feature, with a reviewer, because they know where the client-supplied value came from. Done to a team it produces a worse model.
"You model the whole system annually." You model per change: a new flow gets the six questions. Per-change modelling against a durable diagram is what makes the method affordable.
Interview delivery note
Say this verbatim: "STRIDE is a checklist so you enumerate rather than imagine: draw the data-flow diagram, mark the trust boundaries because every real threat crosses one, and ask the six questions of each element. The two it reliably finds that ad hoc review misses are repudiation, because nobody brainstorms 'can they deny this,' and toxic combinations, a chain that is safe at each step and dangerous end to end." The method and the two categories that justify it.
The senior-versus-staff separator is the disposition, not the enumeration. A senior engineer produces a thorough list of threats. A staff engineer produces a short list of decisions: this one we mitigate, this one we eliminate, this one we accept because Stripe makes it impossible and we documented why. Treating "accept, documented" as a first-class outcome, and treating the deliverable as an eight-row table rather than a forty-threat document, is what makes a threat model useful rather than a compliance artifact.
The second signal is the toxic combination. Noticing that a support agent who can create orders and approve refunds can refund a fake order, which no single STRIDE cell catches, shows you model the system rather than the elements, and separation of duties as the fix is the standard answer to that standard shape.
Further reading
- Adam Shostack, Threat Modeling: Designing for Security, the standard text on STRIDE and data-flow diagrams.
- The Threat Modeling Manifesto (2020), for the four framing questions and the values.
- Microsoft's threat modelling documentation and the Threat Modeling Tool, as the reference implementation of DFD-plus-STRIDE.
- OWASP's threat modelling resources and pytm, for keeping threat models as code alongside the system.