tyler

2026-03-1517 minsecuritythreat-modelingstride

STRIDE: A Worked Threat Model and Its Limits

A full STRIDE threat model on a vehicle telematics backend, then where the taxonomy breaks down: coverage limits, ranking gaps, and privacy blind spots.

STRIDE is a checklist for one question: what can go wrong with this design? You point it at a diagram of your system and walk through six categories of threat, element by element, so you are less likely to forget a whole class of problem.

That is all it is. STRIDE is a taxonomy, not a methodology, and it is not a risk score. It tells you a threat might exist. It does not tell you how likely it is, how bad it would be, or what to do about it. Most of the confusion around STRIDE comes from expecting it to do work it was never built to do.

This post explains STRIDE from first principles, runs a full threat model on a vehicle telematics backend, and then gets specific about where the framework falls short, with the empirical evidence to back it up.

What STRIDE is, and what it isn’t

STRIDE is a mnemonic for six categories of threat. Each one is the inverse of a security property you want to hold. That pairing is the whole idea: if you know the property you are trying to preserve, the threat is whatever violates it.

LetterThreatProperty it breaksConcrete exampleTypical mitigations
SSpoofingAuthenticationStolen credentials, a forged token, a cloned device certificateStrong authentication, MFA, signed tokens, mutual TLS
TTamperingIntegrityAltering a request parameter, poisoning a build artifact, the Superfish ad-injection caseSignatures, MACs, TLS, access control, signed updates
RRepudiationNon-repudiationPerforming an action and later denying it, deleting the record that proves itAppend-only audit logs, signed log entries, off-box logging
IInformation disclosureConfidentialitySniffing data in transit, reading a file you shouldn’t, side channels like SpectreEncryption in transit and at rest, access control, data minimization
DDenial of serviceAvailabilityFlooding a service, exhausting CPU or disk, a connection stormRate limiting, quotas, autoscaling, load shedding
EElevation of privilegeAuthorizationA bug that lets an unprivileged caller run as admin, an authorization bypassLeast privilege, server-side authz checks, sandboxing, input validation

The mnemonic earns its keep because it forces coverage. You will not forget to ask about repudiation when the R is sitting right there in front of you. That is a real benefit. People who threat model without a structure tend to fixate on the attack they read about last week and skip the boring categories.

STRIDE came out of Microsoft in 1999. Loren Kohnfelder and Praerit Garg wrote it up in an internal paper called “The Threats to our Products.”[^memo] It later became the threat-categorization step inside the Microsoft Security Development Lifecycle, and Adam Shostack, who worked on the SDL, did most of the work of explaining it to everyone else, including in his 2014 book Threat Modeling: Designing for Security. Shostack is also the one who keeps repeating the part people miss: STRIDE is a taxonomy of threats, not a process for finding them.

The process he pairs it with is four questions. What are we working on? What can go wrong? What are we going to do about it? Did we do a good job? STRIDE only answers the second one. Everything in this post lives inside that second question, and you should keep the other three in view, because a list of threats with no decisions attached is just anxiety in a spreadsheet.

Start with a diagram

STRIDE needs something concrete to work against. You run it against a data flow diagram (DFD), which has exactly four kinds of thing in it plus one annotation:

  • External entities: people or systems you do not control. Users, third-party APIs, another team’s service. Drawn as rectangles.
  • Processes: code you run. Services, daemons, functions. Drawn as circles.
  • Data stores: where data sits. Databases, files, queues, caches. Drawn as two parallel lines.
  • Data flows: data moving between the above. Drawn as arrows.
  • Trust boundaries: the lines where the privilege or trust level changes. The edge of your network, the gap between a browser and your server, the point where one tenant’s data meets another’s. Drawn as dashed lines.

DFDs predate STRIDE by decades; they come out of structured-analysis work by Yourdon and Constantine in the 1970s. They survived because they are the right level of abstraction for this. Detailed enough to reason about data crossing boundaries, abstract enough that you can draw one for a feature in twenty minutes.

Trust boundaries are where you should spend your attention. Most real compromises happen where data crosses from less-trusted to more-trusted, because that crossing is where someone forgot to validate, authenticate, or authorize. If you are short on time, threat model only the boundary crossings and you will catch most of what matters.

STRIDE-per-element tells you which categories apply to which kind of DFD element. Not every threat applies to every element, and the table below is the standard mapping from Shostack’s book:

DFD elementSTRIDE
External entity
Process
Data flow
Data store

The logic holds up under inspection. You do not spoof a data flow; you spoof the thing at one end of it. You do not elevate privilege inside a database row. A data store gets a parenthetical R because it is subject to repudiation only when it holds logs, in which case tampering with the store is how you repudiate. Processes get all six because a running process is the richest target you have.

There is a second variant worth knowing about. STRIDE-per-interaction, developed by Larry Osterman and Douglas MacIver at Microsoft, enumerates threats against interactions rather than elements: each (source, destination, data flow) tuple, especially across trust boundaries. It exists because per-element tends to under-count the cross-boundary threats where the real bugs live. The tradeoff is that it generates more candidate threats and is genuinely hard to do from memory. I will come back to which one to use, though it probably matters less than you’d think.

A worked threat model: vehicle telematics

Here is a system I’ll model end to end. A fleet of vehicles, each with a telematics control unit (TCU), checks in to a backend over the cellular network. The backend ingests telemetry (location, diagnostics) and can send commands back down to the vehicle, including safety-relevant ones like remote door unlock and triggering an over-the-air update. A human fleet operator manages all of this from a web dashboard. This is just a general example telematics backend that does not represent any real telematics back end services or data collection practices.

I’m modeling the backend and its edges, not the internals of the vehicle. You have to draw a boundary somewhere, and the backend is where the interesting identity questions live: proving that a vehicle is the vehicle it claims to be, and proving who sent a command.

flowchart LR TCU["Vehicle TCU"] OP["Fleet operator browser"] subgraph backend["Trusted backend"] GW(("Ingest Gateway")) SVC(("Telematics Service")) subgraph datatier["Data tier"] DB[("Telemetry + Command DB")] end end TCU -->|"telemetry + commands, mutual TLS"| GW OP -->|"HTTPS"| SVC GW --> SVC SVC <-->|"reads + writes"| DB style backend stroke-dasharray: 5 5 style datatier stroke-dasharray: 5 5

Circles are processes, the cylinder is the data store, and the loose rectangles are the external entities. The dashed boxes are trust boundaries: the outer one wraps the backend, and the inner one marks the jump from the service into the database. The two external entities cross that outer boundary by different routes, the TCU over the cellular network and the operator over the public internet, which is why their threats look different.

Now walk the elements. I am not going to list all six categories for all four elements, partly because the table above already tells you which apply, and partly because the full enumeration runs to about two dozen candidate threats for this tiny diagram. That sprawl is itself the first lesson, and I’ll pick it up in the limitations section. Here are the ones that matter.

Vehicle TCU (external entity: S, R). Spoofing is the headline threat. If an attacker extracts a vehicle’s private key, or gets a rogue device enrolled, the backend will treat that attacker as a legitimate vehicle and act on its telemetry and accept it as a command target. The mitigation is identity that is hard to clone: per-vehicle key material held in a hardware secure element, mutual TLS so the vehicle authenticates to the backend and vice versa, certificates chained to a provisioning root established at manufacture, and short certificate lifetimes with rotation so a leaked key has a shelf life. Repudiation matters because of the command path: when the backend issues a remote unlock, you want non-repudiable proof of who issued it and that the vehicle received it, or a future dispute (or incident investigation) has nothing to stand on.

Ingest Gateway and Telematics Service (processes: all six). Two threats here are worth calling out. The first is elevation of privilege through broken authorization. If the gateway accepts an expired or revoked certificate, or the service fails to check that operator A is allowed to command vehicle B, you get cross-tenant command execution: one customer issuing remote unlocks to another customer’s vehicles. That is about as bad as it gets for this system, because it is a safety and a privacy failure at once. Mitigations are peer-to-peer isolation, strict certificate-chain and revocation checking (OCSP stapling, or just short-lived certs so revocation matters less) plus an authorization check on every command, server-side, scoped to the tenant. The second is denial of service, and the interesting version is not malicious. When a regional cell network blips and recovers, hundreds of thousands of TCUs reconnect in the same few seconds. That reconnect storm will take the gateway down if you let it. The fix lives partly in the client (reconnect with exponential backoff and jitter) and partly in the backend (rate limiting, load shedding, autoscaling).

Data flows (T, I, D). The TCU-to-gateway link carries both telemetry up and commands down. Tampering: without integrity protection, an on-path attacker rewrites a command in flight, changing the target or flipping a flag. Mutual TLS handles the transport, but for safety-relevant commands I would sign the command itself, so integrity survives even after TLS terminates at the gateway and the command moves through internal hops. Information disclosure: location telemetry is sensitive personal data. Anyone who can read it learns where a specific person drives, which is a privacy problem. TLS in transit is the baseline. The harder question is whether you need precise GPS at all, or whether a coarser value would do, which is a data-minimization decision STRIDE will not make alone.

Telemetry and Command DB (data store: T, I, D, and R). Information disclosure is the big one. This store holds months of location history across the whole fleet. A breach here is a privacy catastrophe with a physical-safety dimension. Encrypt at rest with envelope encryption and a KMS, lock down access, tokenize identifiers, and set a retention policy so you are not holding three years of everyone’s movements for no reason. The store also holds the command audit log, which is why repudiation applies: if that log is mutable, an insider can issue a command and then delete the evidence. Append-only or write-once storage, signed entries, and shipping logs off-box close that gap.

Now rank them

This is the step that turns a threat list into a risk assessment, and it is the step STRIDE does not do for you. STRIDE produced the candidates above. It has nothing to say about which one to fix first. So you bring your own prioritization. At design time, before any of these threats is a concrete, testable bug, a likelihood times impact judgment is usually the right tool:

ThreatLikelihoodImpactPriority
Cross-tenant command authz bypass (EoP)MediumHighCritical
TCU key extraction / vehicle spoofingMediumHighHigh
Location history disclosure from DB breachMediumHighHigh
Reconnect-storm DoSHighMediumHigh
Command tampering on the linkLow (if mTLS enforced)HighMedium
Audit-log repudiation (mutable log)LowMediumMedium

Those ratings are judgment calls. Two competent engineers will fill this table in differently, and that subjectivity is a real property of the method, not a failure of this particular example. The value is not precision. The value is that you now have an order, and the order says fix the authorization bypass before you spend a sprint hardening the audit log.

A word on the scoring methods you will see attached to STRIDE. DREAD (Damage, Reproducibility, Exploitability, Affected users, Discoverability) was Microsoft’s original pairing. Microsoft moved away from it around 2008, and the reasons are worth internalizing: the scores are highly subjective, the averaging can hide a single catastrophic factor behind four mild ones, and the Discoverability dimension actively rewards security through obscurity, since a threat scores lower simply for being hard to find. If you use DREAD, treat it as a quick triage heuristic, not a number you defend to an auditor.

CVSS is the industry-standard alternative. CVSS v4.0 is the current version; FIRST published it on November 1, 2023, replacing v3.1 from 2019. It is a good tool with one caveat that matters here: CVSS scores vulnerabilities, not design-stage threats. A STRIDE finding like “the command path might be repudiable” is not yet a vulnerability with an attack vector you can rate. So the honest workflow is to prioritize design threats with a likelihood-impact matrix, and reach for CVSS once a threat has firmed up into a specific, reproducible weakness you can characterize.

Where STRIDE breaks down

STRIDE is useful and I reach for it often. It also has real limits, and knowing them is the difference between a threat model that helps and one that gives false confidence.

It explodes. Six categories times every element, or worse, times every interaction, scales badly. Our four-element toy diagram generated roughly two dozen candidate threats. A real backend with forty services and a dozen data stores generates thousands, most of them either duplicates or not worth writing down. STRIDE-per-interaction makes this worse, not better. The combinatorial blow-up is the single most common reason threat-modeling sessions stall: the team drowns in candidates and quits before ranking anything.

It ranks nothing. Covered above, but it belongs on the list of limitations because it is the most frequently misunderstood one. People treat a STRIDE output as a prioritized findings report. It is an unsorted pile until you do the sorting with a separate method.

The categories overlap and mix levels of abstraction. A single threat often maps to several letters. Steal a vehicle’s certificate and you have spoofing, but that spoofing immediately enables tampering with telemetry and disclosure of location data. One root cause, three letters. Part of why this happens is that STRIDE mixes outcomes (information disclosure, denial of service are things that happen to you) with mechanisms (spoofing, elevation of privilege are things an attacker does). Shostack’s pragmatic advice is the right one: do not get hung up on which letter a threat belongs to. The point is to notice the threat, not to file it correctly.

It is not repeatable. This is the limitation with the most evidence behind it, and the most important. STRIDE’s output depends heavily on the quality of your DFD and the experience of the person running it. Hand the same system to two analysts and you get two different threat lists. The clearest study here is Scandariato, Wuyts and Joosen (2015), who ran Microsoft’s technique with student participants and found a moderately low false-positive rate but a moderately high false-negative rate. In plain terms: the threats STRIDE surfaces tend to be real (decent precision), but it misses a lot of real threats (poor recall), at a working rate of roughly 1.8 valid threats per hour. The participants were students, so do not read the exact numbers as gospel for senior engineers. The direction of the finding is the part that generalizes, and it is sobering. A clean STRIDE pass is not evidence that you found your threats. It is evidence that you looked.

The two variants are roughly a wash. If you have been wondering whether to use STRIDE-per-element or STRIDE-per-interaction, the empirical answer is that it matters less than the marketing suggests. Tuma and Scandariato (2018) compared them in a controlled experiment and found no statistically significant difference in precision or productivity. A 2022 replication by Mbaka and Tuma reached the same conclusion, and even the small edge flipped direction between the two studies. Per-interaction is not the expert’s tool and per-element is not the toy. Pick the one your team will actually finish.

It has blind spots that no amount of skill closes. STRIDE was built for the security properties of the late 1990s, and there are whole categories of modern threat it does not see:

  • Privacy. STRIDE is nearly silent on privacy harms. Linkability, identifiability, and the simple fact of collecting data you should not, none of these are STRIDE categories. This gap is exactly why LINDDUN exists. It was built at KU Leuven, modeled deliberately on STRIDE, to cover the privacy threats STRIDE skips. If your system handles personal data, and a telematics backend full of location history very much does, run LINDDUN alongside STRIDE rather than pretending the I in STRIDE covers it.
  • Business logic. A coupon that can be applied twice, a race condition in a funds transfer, a workflow that can be driven backward. These are real, exploitable, and map to none of the six letters cleanly.
  • Supply chain. The SolarWinds compromise was not a spoofing or tampering finding on anyone’s DFD, because the threat entered through the build pipeline, which most threat models draw as a trusted box if they draw it at all.
  • Social engineering and emerging classes. Phishing is only partially captured under spoofing. And the newer ones, prompt injection against an LLM feature, model poisoning, map onto STRIDE so awkwardly that people have proposed extensions (STRIDE-AI and others) rather than force the fit.

None of this means STRIDE is broken. It means STRIDE is a tool with a shape, and you should know where its edges are so you can bring a second tool when you cross them.

Further reading

← OlderWhen "Not Root" Still Means Root: Linux Capabilities and Least Privilege