{"id":322,"date":"2026-10-24T09:47:00","date_gmt":"2026-10-24T16:47:00","guid":{"rendered":"https:\/\/blog.canutethegreat.com\/?p=322"},"modified":"2026-10-24T09:47:00","modified_gmt":"2026-10-24T16:47:00","slug":"threat-modeling-with-stride-for-a-small-team","status":"publish","type":"post","link":"https:\/\/blog.canutethegreat.com\/index.php\/2026\/10\/24\/threat-modeling-with-stride-for-a-small-team\/","title":{"rendered":"Threat modeling with STRIDE for a small team"},"content":{"rendered":"<p>Most small teams I talk to think threat modeling is something big companies do with a dedicated security architect, a two-week engagement and a 40-page document nobody reads afterwards. That version exists, and it&#8217;s mostly a waste. The version that works for a team of five engineers fits in a 90-minute meeting around a whiteboard and ends with a handful of tickets. STRIDE is the tool I reach for because it gives a room full of people a structured way to ask &#8220;what could go wrong here?&#8221; without needing a security specialist to lead every question.<\/p>\n<h2>What STRIDE actually is<\/h2>\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/blog.canutethegreat.com\/wp-content\/uploads\/2026\/10\/threat-modeling-with-stride-for-a-small-team-diagram.png\" alt=\"Data flow diagram of a payment webhook receiver with three trust boundaries and STRIDE threat badges on each boundary-crossing flow\" \/><\/figure>\n<p>STRIDE came out of Microsoft in 1999, from Loren Kohnfelder and Praerit Garg, and was popularised by Adam Shostack&#8217;s book <em>Threat Modeling: Designing for Security<\/em>. It&#8217;s a mnemonic for six threat categories, each one the violation of a property you want your system to have:<\/p>\n<ul>\n<li>Spoofing violates authentication. Someone claims to be a user, a service or a host they aren&#8217;t.<\/li>\n<li>Tampering violates integrity. Data in transit, at rest or in memory gets modified.<\/li>\n<li>Repudiation violates non-repudiation. Someone does something and you can&#8217;t prove it.<\/li>\n<li>Information disclosure violates confidentiality.<\/li>\n<li>Denial of service violates availability.<\/li>\n<li>Elevation of privilege violates authorization. A user or component gets capabilities it shouldn&#8217;t have.<\/li>\n<\/ul>\n<p>That&#8217;s it. It isn&#8217;t a methodology on its own, and it doesn&#8217;t rank anything. It&#8217;s a checklist of question shapes. The value is that it stops the discussion from collapsing into whatever the loudest person in the room worried about last week, which in my experience is usually SQL injection.<\/p>\n<h2>Start from four questions, not from the acronym<\/h2>\n<p>Shostack frames the whole exercise as four questions, and the Threat Modeling Manifesto (2020) kept them:<\/p>\n<ol>\n<li>What are we working on?<\/li>\n<li>What can go wrong?<\/li>\n<li>What are we going to do about it?<\/li>\n<li>Did we do a good enough job?<\/li>\n<\/ol>\n<p>STRIDE only answers question two. Teams that skip question one end up applying STRIDE to a vague mental picture of &#8220;the system&#8221;, which produces vague threats like &#8220;an attacker could spoof a user&#8221;. True, useless.<\/p>\n<p>So answer question one concretely. Pick one feature or one change, not the whole platform. &#8220;We&#8217;re adding a webhook receiver that accepts events from our payment provider and updates order status&#8221; is a good scope. &#8220;Our backend&#8221; is not.<\/p>\n<h2>Draw a data flow diagram with trust boundaries<\/h2>\n<p>The diagram is where most of the value comes from, and it should take 15 to 20 minutes. You need four kinds of things:<\/p>\n<ul>\n<li>External entities: users, third-party services, anything you don&#8217;t control.<\/li>\n<li>Processes: your services, workers, lambdas.<\/li>\n<li>Data stores: databases, queues, buckets, caches.<\/li>\n<li>Data flows: arrows between them, labelled with what moves and over what protocol.<\/li>\n<\/ul>\n<p>Then draw trust boundaries, the dashed lines where the level of trust changes. Internet to your load balancer. Your API to the database. Your worker to the third-party API. Your app to the admin interface. If a flow crosses a dashed line, that&#8217;s where you spend your time. Flows entirely inside one trust zone rarely produce interesting threats.<\/p>\n<p>For the webhook example, the boundaries are the payment provider to your public endpoint, your receiver to the queue, and the worker to the orders database. Three crossings. That&#8217;s a manageable meeting.<\/p>\n<h2>Walk STRIDE per element<\/h2>\n<p>The variant that works best for small teams is STRIDE-per-element. Not every category applies to every kind of element, and the classic mapping keeps the discussion short:<\/p>\n<ul>\n<li>External entities: spoofing and repudiation.<\/li>\n<li>Processes: all six.<\/li>\n<li>Data stores: tampering, information disclosure, denial of service, and repudiation if the store is a log.<\/li>\n<li>Data flows: tampering, information disclosure, denial of service.<\/li>\n<\/ul>\n<p>Go element by element along the flows that cross a boundary and ask each applicable question out loud. For the webhook receiver it might go like this:<\/p>\n<ul>\n<li>Spoofing: can anyone on the internet POST a fake &#8220;payment succeeded&#8221; event? Do we verify the provider&#8217;s signature, and do we compare it in constant time?<\/li>\n<li>Tampering: can a valid event be replayed? Is there a timestamp in the signed payload, and do we reject old ones?<\/li>\n<li>Repudiation: if an order flips to paid by mistake, can we show which event did it and where it came from?<\/li>\n<li>Information disclosure: do our error responses leak stack traces or internal hostnames to the caller?<\/li>\n<li>Denial of service: what happens if someone sends 10,000 large bodies a second? Is there a size limit before we parse JSON?<\/li>\n<li>Elevation of privilege: does the worker&#8217;s database role need to write to anything except the orders table?<\/li>\n<\/ul>\n<p>Every one of those is a concrete, testable question, and most of them are a few lines of code to fix. That&#8217;s the point.<\/p>\n<h2>Record threats as tickets, not as a document<\/h2>\n<p>The output of the session is a list of threats, each with a decision. Mitigate, accept, transfer or eliminate the feature. I like a plain table in the repo next to the code, because it gets reviewed in pull requests and it ages with the system:<\/p>\n<pre class=\"wp-block-code\"><code>| ID | Element            | STRIDE | Threat                                  | Decision  | Ticket   |\n|----|--------------------|--------|-----------------------------------------|-----------|----------|\n| W1 | Webhook endpoint   | S      | Forged event without valid signature    | Mitigate  | PAY-412  |\n| W2 | Webhook endpoint   | T      | Replay of a captured valid event        | Mitigate  | PAY-413  |\n| W3 | Receiver -&gt; queue  | D      | Unbounded body size exhausts memory     | Mitigate  | PAY-414  |\n| W4 | Worker DB role     | E      | Role can write to users table           | Mitigate  | PAY-415  |\n| W5 | Provider outage    | D      | Events delayed for hours                | Accept    | -        |<\/code><\/pre>\n<p>Accepted risks are fine. Write down who accepted them. A threat model where everything is &#8220;mitigate&#8221; usually means nobody was honest about the effort.<\/p>\n<h2>Making it stick on a small team<\/h2>\n<p>The failure mode isn&#8217;t doing threat modeling badly. It&#8217;s doing it once at a kickoff and never again. A few habits keep it alive.<\/p>\n<p>Tie it to design review. If a change adds a new trust boundary crossing, a new external entity or a new data store with sensitive data, it gets a STRIDE pass. Everything else doesn&#8217;t. That keeps it rare enough that people don&#8217;t resent it.<\/p>\n<p>Rotate who facilitates. The facilitator&#8217;s job is to keep the group moving through the elements and to stop the session from becoming a design argument. Any engineer can do that after watching it twice, and the person who facilitates tends to start thinking this way in code review too.<\/p>\n<p>Keep the diagram in the repo as text. A diagram drawn in a SaaS whiteboard is dead the day the meeting ends. Mermaid, PlantUML or OWASP Threat Dragon&#8217;s JSON files all diff fine. Microsoft&#8217;s Threat Modeling Tool is also free if your team is on Windows and wants STRIDE suggestions generated from the diagram, though I find the generated list too long for a small team to triage.<\/p>\n<p>Revisit question four. A month after the feature ships, spend ten minutes asking whether any incident, bug report or pentest finding touched an element you modeled. If one did and the model missed it, that&#8217;s the most useful feedback you&#8217;ll get about your process.<\/p>\n<h2>Where STRIDE falls short<\/h2>\n<p>STRIDE is good at technical threats along data flows. It&#8217;s weak on abuse of legitimate functionality, like a referral program being farmed, and on privacy harms, where LINDDUN is a better fit. It also says nothing about likelihood. When a session produces more threats than you can fix, rank them by what an attacker can reach from outside your trust boundaries first, and by what you&#8217;d have to explain in an incident report second. That&#8217;s crude, and it&#8217;s still better than ranking by gut feeling about which bug sounds scariest.<\/p>\n<p>I&#8217;ve watched a five-person team find a missing webhook signature check, an admin endpoint reachable from the public load balancer and a worker with database superuser rights, all in one 90-minute session. Nobody in the room was a security specialist. They just drew the thing and asked the questions in order.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Most small teams I talk to think threat modeling is something big companies do with a dedicated security architect, a two-week engagement and a 40-page document nobody reads afterwards. That version exists, and it&#8217;s mostly a waste. The version that works for a team of five engineers fits in a 90-minute meeting around a whiteboard [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":325,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[21,26],"tags":[260,261,262,263],"class_list":["post-322","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-security","category-software-engineering","tag-data-flow-diagrams","tag-secure-design","tag-stride","tag-threat-modeling"],"_links":{"self":[{"href":"https:\/\/blog.canutethegreat.com\/index.php\/wp-json\/wp\/v2\/posts\/322","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.canutethegreat.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.canutethegreat.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.canutethegreat.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.canutethegreat.com\/index.php\/wp-json\/wp\/v2\/comments?post=322"}],"version-history":[{"count":1,"href":"https:\/\/blog.canutethegreat.com\/index.php\/wp-json\/wp\/v2\/posts\/322\/revisions"}],"predecessor-version":[{"id":456,"href":"https:\/\/blog.canutethegreat.com\/index.php\/wp-json\/wp\/v2\/posts\/322\/revisions\/456"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.canutethegreat.com\/index.php\/wp-json\/wp\/v2\/media\/325"}],"wp:attachment":[{"href":"https:\/\/blog.canutethegreat.com\/index.php\/wp-json\/wp\/v2\/media?parent=322"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.canutethegreat.com\/index.php\/wp-json\/wp\/v2\/categories?post=322"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.canutethegreat.com\/index.php\/wp-json\/wp\/v2\/tags?post=322"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}