Skip to main content
友田 陽大
Security engineering & career
セキュリティ
脅威モデリング
STRIDE
セキュア設計
セキュリティエンジニア

A practical threat-modeling guide [2026 edition]: crushing vulnerabilities at the 'design stage' with STRIDE and data flow diagrams

A practical guide to threat modeling for building security into the design stage. It explains, with real code faithful to official information: the Threat Modeling Manifesto's four questions, STRIDE's six categories, how to draw a data flow diagram and trust boundaries, and how to manage threats 'as code' and continuously verify them in CI.

Published
Reading time
10 min read
Author
友田 陽大
Share

Security defects are cheaper the earlier you find them — this is an industry iron rule. If a vulnerability is found after production release, the cost is unlimited: investigation, fix, redeploy, and in some cases responding to information leakage. On the other hand, find it on a diagram at the design stage and it's just an erase with an eraser.

The systematic method for this "building security in at the design stage" is threat modeling. This article, rather than how to use abstruse tools, explains a thinking type a team can run right away, faithful to official information (the Threat Modeling Manifesto, Microsoft's STRIDE, OWASP Threat Modeling), and finally goes all the way to the implementation of managing threats "as code."

The positioning of this article: of a security engineer's core skills, this deeply explores the "Identify" in NIST CSF 2.0 terms — the ability to surface risk at the design stage. For the roadmap of the role overall, see how to become a security engineer, and for the technique of crushing the surfaced threats in implementation, secure-coding practices.


0. What threat modeling is — just answer four questions

When you hear threat modeling, you tend to imagine a dedicated tool or abstruse notation, but the essence is answering, as a team, the four questions the Threat Modeling Manifesto raises.

  1. What are we working on? — diagram the target system.
  2. What can go wrong? — surface the threats.
  3. What are we going to do about it? — decide on mitigations.
  4. Did we do a good enough job? — verify.

Running these four questions for an hour in a design-review setting is already valuable. The Manifesto says — "a valuable model over a perfect model," "people and collaboration over checklists." Threat modeling isn't a "ritual" but a collaborative activity to think one level deeper about the design.


1. STRIDE — the six categories for exhaustively enumerating threats

Listing "what can go wrong" by "feel" will surely leave gaps. So use STRIDE, systematized by Microsoft. It divides threats into six categories, each corresponding to which security property it breaks.

STRIDEThreatProperty brokenExample
SpoofingSpoofingAuthenticationLog in by impersonating someone else
TamperingTamperingIntegrityRewrite a request or stored data
RepudiationRepudiationNon-repudiationWriggle out with "I didn't do it" (poor logs)
Information DisclosureInformation disclosureConfidentialityPeek at someone else's data
Denial of ServiceDenial of serviceAvailabilityStop the service with mass requests
Elevation of PrivilegePrivilege escalationAuthorizationA general user gains admin privileges

STRIDE's strength is that you can mechanically ask the six "is there spoofing for this element? tampering? …" Exhaustiveness beats person-dependent intuition.


2. Draw a data flow diagram (DFD) and trust boundaries

The best tool to share "what we're working on" is a Data Flow Diagram (DFD). More than perfect notation, what matters is seeing where data flows from and to, and where the 'premise of trust' changes.

The minimal vocabulary is just this.

  • External Entity: users, external APIs (which we don't control)
  • Process: API servers, functions (our code)
  • Data Store: DB, cache, files
  • Data Flow: data flowing between elements (arrows)
  • Trust Boundary: the line where the trust level changes (most important)
        ┌─────────────[ 信頼境界 ]─────────────┐
            (境界の外=信頼できない)

 [ユーザー] ──HTTPリクエスト──▶ │ [APIサーバー] ──SQL──▶ [DB]
  外部実体                       │   プロセス              データストア
                               │       │
 [外部決済API] ◀──HTTPS────────  │       └──ログ──▶ [ログ基盤]
  外部実体                       └──────────────────────────┘

The trust boundary is the very heart of threat modeling. Data flows that cross the boundary — user input, external-API responses, calls from another microservice — are all "the moment something untrusted enters the inside." This coincides with where validation, authentication, and authorization should take effect. Once the boundary is visible on the diagram, "where to protect" becomes self-evident.


3. STRIDE × DFD — concretely enumerate threats

Apply STRIDE's six categories to each element of the DFD. For example, about the "user → API server" data flow (crossing the boundary):

ElementSTRIDEAssumed threatMitigation
User authenticationSpoofingImpersonation via a weak password / token theftMFA, strong authentication, short-lived tokens
Request bodyTamperingParameter tampering (amount, ID)Input validation at the boundary, signing
Recording of operationsRepudiationDenying with "I didn't order"Tamper-resistant audit logs
Data-retrieval APIInformation DisclosureViewing others' data via IDORServer-side authorization, RLS
Public endpointDenial of ServiceExhaustion via mass requestsRate limiting, timeouts
Permission checkElevation of PrivilegeA general user hits the admin APILeast privilege, server-side role verification

In this way, just filling in the "element × STRIDE" matrix makes the threat enumeration astonishingly exhaustive. You don't need to crush everything perfectly. Being "aware of it" is decisively more important than not being aware.


4. Manage the threat model "as code"

The biggest reason threat modeling becomes a formality is "writing it in a document once and letting it rot as-is." The design keeps changing, but the diagram fossilizes as a PNG somewhere on a wiki. The best way to prevent this is to manage the threat model as code, in the repository (Threat Modeling as Code).

First, hold the threat registry as typed structured data.

# threat-model.yaml — 脅威レジストリ(リポジトリで版管理する)
threats:
  - id: T-001
    component: "注文取得API"
    stride: InformationDisclosure
    description: "URLのIDを改ざんし、他人の注文を閲覧できる(IDOR)"
    risk: high          # high | medium | low(可能性 × 影響)
    mitigation: "サーバー側で所有者条件をクエリに焼き込み、RLSで多層防御する"
    mitigated: true     # 緩和策が実装済みか
  - id: T-002
    component: "ログインAPI"
    stride: Spoofing
    description: "総当たりでパスワードを推測される"
    risk: high
    mitigation: "レート制限 + アカウントロック + 漏えいパスワードのブロックリスト照合"
    mitigated: true
  - id: T-003
    component: "管理ダッシュボード"
    stride: ElevationOfPrivilege
    description: "一般ユーザーが管理APIを直接叩いて権限昇格する"
    risk: high
    mitigation: "全管理APIでサーバー側ロール検証を必須化する"
    mitigated: false    # ← まだ未対応

Next, write a verification that "if a high-risk threat has no mitigation / unimplemented, fail CI." This makes the threat model a "living contract."

// verify-threat-model.ts — 高リスクの未緩和脅威があればCIを落とす(設計と実装の乖離を防ぐ)。
import { readFileSync } from "node:fs";
import { parse } from "yaml";
import { z } from "zod";

// ① スキーマで脅威モデルを検証(型の不正・記入漏れを起動時に弾く)。
const ThreatSchema = z.object({
  id: z.string().regex(/^T-\d{3}$/),
  component: z.string().min(1),
  stride: z.enum([
    "Spoofing", "Tampering", "Repudiation",
    "InformationDisclosure", "DenialOfService", "ElevationOfPrivilege",
  ]),
  description: z.string().min(1),
  risk: z.enum(["high", "medium", "low"]),
  mitigation: z.string().min(1),
  mitigated: z.boolean(),
});
const ModelSchema = z.object({ threats: z.array(ThreatSchema) });

function verify(path: string): number {
  const model = ModelSchema.parse(parse(readFileSync(path, "utf8")));

  // ② 不変条件:高リスクの脅威は、必ず緩和策が実装済みでなければならない。
  const unmitigated = model.threats.filter((t) => t.risk === "high" && !t.mitigated);

  if (unmitigated.length > 0) {
    console.error("❌ 高リスクなのに未緩和の脅威があります:");
    for (const t of unmitigated) console.error(`  - ${t.id} (${t.component}): ${t.description}`);
    return 1; // CIを失敗させる
  }
  console.log(`✅ ${model.threats.length} 件の脅威すべてに緩和策が実装済みです。`);
  return 0;
}

process.exit(verify("threat-model.yaml"));

Place this script in CI and you can mechanically detect the divergence between "threats raised in design" and "implemented mitigations." The threat model changes from a "rotting document" into an "unbreakable contract" — this is a security-engineer-like design practice. The thinking on type-safe boundary validation connects to secure-coding practices.


5. Prioritizing mitigations — don't try to protect everything

Even if you raise 100 threats, resources are finite. Prioritize by risk = likelihood × impact.

Impact: largeImpact: small
Likelihood: highMitigate as top priorityMitigate early
Likelihood: lowMitigate or accept (record required)Accept (record only)

What's important is that "acceptance" is also a legitimate option. You can't zero every risk. A state where you can record "this risk is accepted, with a reason" is far healthier than unwittingly leaving it. This is also a decision that management should be responsible for, and the METI Cybersecurity Management Guidelines also position grasping risk and decision-making as a management responsibility.


6. When and how much to do it

Threat modeling isn't "do it once and you're done."

  • When designing a new feature: the most cost-effective. Build 30 minutes to 1 hour of threat modeling into the design review.
  • When the architecture changes: when the trust boundary moves (new external integration, a change of authentication method, etc.).
  • Periodically: review the existing model, e.g., once a quarter.

"Light, frequent" is the principle. Rather than doing heavy threat modeling once a year, continuously running lightweight threat modeling per feature keeps up with change and takes root in the team as a culture.


7. Frequently asked questions (FAQ)

Q. Is threat modeling needed even for a small team / solo development? A. It is. Rather, the fewer the people, the greater the value of preventing rework at the design stage. No dedicated tool is needed. You can start by drawing a DFD on a whiteboard and asking STRIDE's six items.

Q. Should I use a dedicated tool? A. Not at first. There's the Microsoft Threat Modeling Tool and OWASP Threat Dragon, etc., but the essence is the thinking of "the four questions" and "STRIDE × DFD." Use tools once you're accustomed.

Q. What's the difference from methods other than STRIDE (PASTA, LINDDUN, etc.)? A. STRIDE has the lowest learning cost and is general-purpose. There are purpose-specific methods like LINDDUN for privacy focus and PASTA for risk-driven, but the royal road is to first get the type into your body with STRIDE.

Q. What's the difference from vulnerability assessment (penetration testing)? A. Threat modeling is the "preventive" activity of surfacing threats at the design stage, and assessment is the "discovery" activity of searching for vulnerabilities after implementation. They're two wheels. For how to do assessment, see web-app vulnerability assessment, and for the attacker's viewpoint, white-hat hacker introduction.

Q. Doesn't it slow down development? A. In the short term, design time increases. But threats crushed at the design stage are an order of magnitude cheaper than the post-release incident-response cost (response per NIST 800-61). Overall development speed actually rises.


8. Conclusion

Threat modeling is, not special talent or an expensive tool, a collaborative activity to think one level deeper about the design.

  • Run the four questions. What are we working on / what can go wrong / what to do / did we do a good enough job.
  • Be exhaustive with STRIDE × DFD. Draw trust boundaries and apply the six categories to each element. Beat intuition with exhaustiveness.
  • Manage it as code. Hold the threat registry in YAML, verify the presence of mitigations in CI, and prevent the divergence of design and implementation.
  • Prioritize, and record acceptance too. You can't protect everything. Being aware is far healthier than leaving it.
  • Light, frequent. Keep running it per feature and let it take root as a culture.

Whether you can build security in at the design stage most greatly affects a product's safety. "Building fast" and "building safely" can be reconciled in the first 30 minutes of design — that's the essence of threat modeling. If you want to build threat modeling into your product's design review, or take inventory of the "vertical risks" of an existing design once, feel free to consult me.


References (official primary sources)

友田

友田 陽大

Developer of a METI Minister's Award–winning product. With TypeScript + Python + AWS, I deliver SaaS, industry DX, and production-grade generative AI (RAG) end to end — from requirements to infrastructure and operations — single-handedly.

I can take on the implementation from this article as an engagement

Security engineering, from design to implementation and operations

Design reviews via threat modeling, correct implementation of crypto and authn/authz, log design and detection (detection engineering), and building an incident-response capability. With experience building — solo × generative AI — a METI Minister's Award B2B SaaS and a payments platform with zero double charges in production, I help get your product to a state where it ships fast and can be defended. Vertical risks that only design can address, I also take on as an audit.

Available for both project-based (contract) and advisory engagements. Start with a free 30-minute consult.

Also worth reading