devsecops

Performing Threat Modeling with OWASP Threat Dragon

Use OWASP Threat Dragon to create data flow diagrams, identify threats using STRIDE and LINDDUN methodologies, and generate threat model reports for secure design review.

data-flowdfdlinddunowaspsecure-designstridethreat-dragonthreat-modeling
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Overview

OWASP Threat Dragon is an open-source threat modeling tool that enables security teams and developers to create threat model diagrams, identify threats using established methodologies (STRIDE, LINDDUN, CIA, DIE, PLOT4ai), and generate comprehensive reports. Threat Dragon runs as both a web application and desktop application (Windows, macOS, Linux), supporting distributed teams working collaboratively on threat models. Version 2.x provides drag-and-drop diagram creation, an auto-generation rule engine for threats and mitigations, and PDF report output for documentation and GRC compliance.

When to Use

  • When conducting security assessments that involve performing threat modeling with owasp threat dragon
  • When following incident response procedures for related security events
  • When performing scheduled security testing or auditing activities
  • When validating security controls through hands-on testing

Prerequisites

  • OWASP Threat Dragon desktop application or web instance
  • Understanding of data flow diagram (DFD) notation
  • Familiarity with STRIDE or LINDDUN threat classification
  • Application architecture documentation and network diagrams
  • Stakeholder access for design review sessions

Threat Modeling Methodologies

STRIDE

Category Threat Type Description Example
S Spoofing Impersonating a user or system Stolen session tokens
T Tampering Modifying data in transit or at rest SQL injection altering records
R Repudiation Denying an action occurred Missing audit logs
I Information Disclosure Exposing sensitive data API returning excessive fields
D Denial of Service Making a service unavailable Resource exhaustion attack
E Elevation of Privilege Gaining unauthorized access Broken access control

LINDDUN (Privacy-Focused)

Category Threat Type Description
L Linkability Associating data items across contexts
I Identifiability Identifying an individual from data
N Non-repudiation Inability to deny an action (privacy risk)
D Detectability Determining if data about a subject exists
D Disclosure Exposing personal information
U Unawareness User unaware of data collection
N Non-compliance Violating privacy regulations

Workflow

Step 1 --- Install Threat Dragon

Desktop Application: Download the installer from the OWASP Threat Dragon releases page for Windows (.exe), macOS (.dmg), or Linux (.AppImage/.deb/.rpm).

Web Application (Docker):

docker run -p 3000:3000 \
  -e ENCRYPTION_JWT_SIGNING_KEY=$(openssl rand -hex 32) \
  -e ENCRYPTION_JWT_REFRESH_SIGNING_KEY=$(openssl rand -hex 32) \
  -e ENCRYPTION_KEYS='[{"isPrimary":true,"id":0,"value":"'$(openssl rand -hex 16)'"}]' \
  -e NODE_ENV=production \
  owasp/threat-dragon:latest

Step 2 --- Define the Scope

Before creating diagrams, document the scope:

  • System name and description
  • Assets being protected (user data, credentials, payment info)
  • External dependencies (third-party APIs, cloud services)
  • Compliance requirements (GDPR, HIPAA, PCI DSS)
  • Trust boundaries (network segments, authentication zones)

Step 3 --- Create Data Flow Diagrams

In Threat Dragon, create a new threat model and add diagrams using the following DFD elements:

Processes: Applications, microservices, API endpoints that transform data. Represented as circles/rounded rectangles.

Data Stores: Databases, file systems, caches, message queues that persist data. Represented as parallel lines.

External Entities: Users, external systems, third-party services outside the trust boundary. Represented as rectangles.

Data Flows: Communication channels between elements showing data direction. Represented as arrows with labels describing the data.

Trust Boundaries: Dashed lines separating zones of different trust levels (internet/DMZ/internal network, user/admin).

Step 4 --- Identify Threats

For each DFD element, apply the STRIDE methodology:

Element Type Applicable STRIDE Categories
External Entity Spoofing, Repudiation
Process Spoofing, Tampering, Repudiation, Information Disclosure, DoS, Elevation of Privilege
Data Store Tampering, Information Disclosure, DoS
Data Flow Tampering, Information Disclosure, DoS

Threat Dragon's rule engine automatically suggests threats based on element types. Review each suggestion and mark as:

  • Mitigated: Existing controls address the threat
  • Not Applicable: Threat does not apply to this context
  • Open: Threat needs to be addressed (assign priority and owner)

Step 5 --- Define Mitigations

For each open threat, document:

  • Mitigation strategy (prevent, detect, respond, transfer)
  • Specific technical controls (encryption, authentication, rate limiting)
  • Owner responsible for implementation
  • Priority and timeline for remediation

Step 6 --- Generate Reports

Threat Dragon produces PDF reports containing:

  • Executive summary of the threat model
  • Data flow diagrams with annotations
  • Threat inventory with severity ratings
  • Mitigation status and recommendations
  • Compliance mapping where applicable

Step 7 --- Integrate into SDLC

  • Conduct threat modeling during the design phase of new features
  • Update threat models when architecture changes occur
  • Review threat models during security design reviews
  • Store threat model files in version control alongside code
  • Reference threat model findings in security acceptance criteria

Threat Model File Format

Threat Dragon uses JSON format for threat models, enabling version control and programmatic manipulation:

{
  "version": "2.2.0",
  "summary": {
    "title": "E-Commerce Application",
    "owner": "Security Team",
    "description": "Threat model for the checkout flow"
  },
  "detail": {
    "contributors": [
      {"name": "Security Architect"}
    ],
    "diagrams": [
      {
        "id": 0,
        "title": "Checkout Flow",
        "diagramType": "STRIDE",
        "cells": []
      }
    ]
  }
}

CycloneDX TMBOM Integration

Threat Dragon participates in the CycloneDX Threat Model Bill of Materials (TMBOM) effort, enabling export to a common format that can be consumed by other threat modeling tools and GRC platforms, preventing vendor lock-in.

Best Practices

  1. Start simple: Begin with high-level DFDs (Level 0) before decomposing into detailed diagrams
  2. Involve developers: Include development team members in threat modeling sessions for realistic threat assessment
  3. Time-box sessions: Limit initial sessions to 90 minutes; iterate in follow-up sessions
  4. Prioritize by risk: Use severity ratings (Critical, High, Medium, Low) to prioritize mitigations
  5. Living documents: Treat threat models as living documents that evolve with the system
  6. Automate where possible: Use the rule engine for initial threat generation, then refine manually

References

Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 3

api-reference.md2.0 KB

API Reference: Threat Modeling with OWASP Threat Dragon

Threat Dragon JSON Model Structure

Field Description
version Threat Dragon version (e.g., "2.2.0")
summary.title Threat model name
summary.owner Model owner
detail.diagrams[] Array of DFD diagrams
detail.diagrams[].cells[] DFD elements within a diagram
detail.diagrams[].diagramType Methodology (STRIDE, LINDDUN, CIA)

DFD Element Types

Type Threat Dragon Class STRIDE Categories
Process tm.Process S, T, R, I, D, E
Data Store tm.Store T, I, D
Data Flow tm.Flow T, I, D
External Entity tm.Actor S, R
Trust Boundary tm.Boundary N/A

STRIDE Categories

Letter Threat Mitigation
S Spoofing Strong authentication, MFA
T Tampering Integrity checks, HMAC
R Repudiation Audit logging
I Information Disclosure Encryption, least privilege
D Denial of Service Rate limiting, auto-scaling
E Elevation of Privilege RBAC, authorization checks

Threat Status Values

Status Description
Open Threat needs mitigation
Mitigated Controls address the threat
Not Applicable Threat does not apply

Docker Deployment

docker run -p 3000:3000 \
  -e ENCRYPTION_JWT_SIGNING_KEY=$(openssl rand -hex 32) \
  -e ENCRYPTION_JWT_REFRESH_SIGNING_KEY=$(openssl rand -hex 32) \
  owasp/threat-dragon:latest

Python Libraries

Library Version Purpose
json stdlib Threat Dragon model serialization
uuid stdlib Generate unique element IDs

References

standards.md1.8 KB

Standards Reference for Threat Modeling

OWASP Threat Modeling Process

  1. Decompose the application: Create DFDs showing data flows, trust boundaries, entry points
  2. Determine and rank threats: Apply STRIDE per element, rank by DREAD or risk matrix
  3. Determine countermeasures and mitigations: Map threats to controls
  4. Review and validate: Peer review the model, validate against architecture

NIST SP 800-154: Guide to Data-Centric System Threat Modeling

  • Identify data assets and their sensitivity levels
  • Map data flows through system components
  • Identify threat actors and attack vectors targeting data
  • Assess risk based on data exposure and impact
  • Document countermeasures protecting data at rest, in transit, and in use

ISO 27005 Risk Assessment Alignment

ISO 27005 Step Threat Dragon Activity
Context establishment Define system scope and trust boundaries
Risk identification STRIDE threat enumeration per DFD element
Risk analysis Severity rating and likelihood assessment
Risk evaluation Prioritize threats by risk score
Risk treatment Define mitigations (mitigate, accept, transfer, avoid)

STRIDE-per-Element Mapping

DFD Element S T R I D E
External Entity x x
Process x x x x x x
Data Store x x x
Data Flow x x x

Threat Severity Rating Scale

Rating Score Description
Critical 9-10 Immediate exploitation possible, severe business impact
High 7-8 Likely exploitation, significant business impact
Medium 4-6 Possible exploitation, moderate business impact
Low 1-3 Unlikely exploitation or minimal impact
workflows.md1.8 KB

Threat Modeling Workflows

Workflow 1: Feature-Level Threat Modeling

Product team proposes new feature
       |
Security champion schedules threat modeling session
       |
Gather architecture docs, API specs, DFDs
       |
Workshop session (60-90 min):
  - Review DFD for new feature
  - Apply STRIDE to each element
  - Identify threats and rank by severity
  - Document mitigations needed
       |
Threats documented in Threat Dragon
       |
Mitigation tasks created in backlog
       |
Security acceptance criteria added to user stories
       |
Feature development includes mitigations
       |
Threat model updated during code review

Workflow 2: Architecture Review Threat Model

New system or major refactor proposed
       |
Create Level 0 DFD (context diagram)
       |
Identify trust boundaries and data sensitivity
       |
Decompose into Level 1 DFDs per subsystem
       |
Apply STRIDE per element in each diagram
       |
Auto-generate threats using Threat Dragon rule engine
       |
Review and refine auto-generated threats
       |
Assign severity ratings
       |
Document existing controls as mitigations
       |
Identify gaps (open threats without mitigations)
       |
Generate PDF report for architecture review board
       |
Remediation plan approved and tracked

Workflow 3: Continuous Threat Modeling in Agile

Sprint planning identifies security-relevant stories
       |
Pull existing threat model from version control
       |
Update DFD to reflect planned changes
       |
Quick STRIDE review (30 min) focused on changes
       |
New threats added, mitigations defined
       |
Threat model committed alongside code changes
       |
Sprint retrospective reviews threat model accuracy
       |
Quarterly full threat model review cycle

Scripts 2

agent.py7.6 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Agent for threat modeling with OWASP Threat Dragon.

Programmatically creates Threat Dragon JSON threat models, applies
STRIDE analysis to DFD elements, manages threat inventory, and
generates summary reports for security design reviews.
"""

import json
import sys
import uuid
from datetime import datetime


STRIDE_BY_ELEMENT = {
    "process": ["Spoofing", "Tampering", "Repudiation",
                "Information Disclosure", "Denial of Service",
                "Elevation of Privilege"],
    "data_store": ["Tampering", "Information Disclosure",
                   "Denial of Service"],
    "data_flow": ["Tampering", "Information Disclosure",
                  "Denial of Service"],
    "external_entity": ["Spoofing", "Repudiation"],
}

STRIDE_MITIGATIONS = {
    "Spoofing": ["Implement strong authentication (MFA)",
                 "Use mutual TLS for service-to-service"],
    "Tampering": ["Use integrity checks (HMAC, digital signatures)",
                  "Implement input validation"],
    "Repudiation": ["Enable comprehensive audit logging",
                    "Use tamper-evident log storage"],
    "Information Disclosure": ["Encrypt data at rest and in transit",
                               "Implement least-privilege access"],
    "Denial of Service": ["Implement rate limiting",
                          "Use auto-scaling and circuit breakers"],
    "Elevation of Privilege": ["Enforce RBAC and least privilege",
                               "Validate authorization on every request"],
}


class ThreatModelAgent:
    """Creates and manages OWASP Threat Dragon threat models."""

    def __init__(self, title, owner="Security Team", description=""):
        self.model = {
            "version": "2.2.0",
            "summary": {
                "title": title,
                "owner": owner,
                "description": description,
                "id": 0,
            },
            "detail": {
                "contributors": [],
                "diagrams": [],
                "diagramTop": 0,
                "reviewer": "",
                "threatTop": 0,
            },
        }
        self.threats = []
        self.threat_counter = 0

    def add_diagram(self, title, diagram_type="STRIDE"):
        """Add a new data flow diagram to the threat model."""
        diagram_id = len(self.model["detail"]["diagrams"])
        diagram = {
            "id": diagram_id,
            "title": title,
            "diagramType": diagram_type,
            "placeholder": f"New {diagram_type} diagram",
            "thumbnail": "",
            "version": "2.2.0",
            "cells": [],
        }
        self.model["detail"]["diagrams"].append(diagram)
        return diagram_id

    def add_element(self, diagram_id, element_type, name,
                    x=100, y=100, description=""):
        """Add a DFD element to a diagram."""
        element_id = str(uuid.uuid4())
        type_map = {
            "process": "tm.Process",
            "data_store": "tm.Store",
            "data_flow": "tm.Flow",
            "external_entity": "tm.Actor",
            "trust_boundary": "tm.Boundary",
        }
        cell = {
            "type": type_map.get(element_type, "tm.Process"),
            "id": element_id,
            "name": name,
            "description": description,
            "position": {"x": x, "y": y},
            "size": {"width": 100, "height": 60},
            "threats": [],
            "hasOpenThreats": False,
        }
        self.model["detail"]["diagrams"][diagram_id]["cells"].append(cell)
        return element_id

    def apply_stride(self, diagram_id, element_id, element_type):
        """Apply STRIDE analysis to a DFD element and generate threats."""
        categories = STRIDE_BY_ELEMENT.get(element_type, [])
        generated = []
        diagram = self.model["detail"]["diagrams"][diagram_id]
        element = next((c for c in diagram["cells"] if c["id"] == element_id), None)
        if not element:
            return []

        for category in categories:
            self.threat_counter += 1
            threat = {
                "id": str(self.threat_counter),
                "title": f"{category} - {element['name']}",
                "type": category,
                "status": "Open",
                "severity": "Medium",
                "description": f"Potential {category.lower()} threat "
                               f"against {element['name']}",
                "mitigation": "; ".join(
                    STRIDE_MITIGATIONS.get(category, ["Review required"])),
                "modelType": "STRIDE",
                "element_id": element_id,
            }
            element["threats"].append(threat)
            element["hasOpenThreats"] = True
            self.threats.append(threat)
            generated.append(threat)

        self.model["detail"]["threatTop"] = self.threat_counter
        return generated

    def update_threat_status(self, threat_id, status, mitigation=None):
        """Update a threat's status (Open, Mitigated, Not Applicable)."""
        for threat in self.threats:
            if threat["id"] == str(threat_id):
                threat["status"] = status
                if mitigation:
                    threat["mitigation"] = mitigation
                return threat
        return None

    def get_threat_summary(self):
        """Summarize threats by status and category."""
        summary = {"total": len(self.threats), "by_status": {},
                   "by_type": {}, "by_severity": {}}
        for t in self.threats:
            summary["by_status"][t["status"]] = \
                summary["by_status"].get(t["status"], 0) + 1
            summary["by_type"][t["type"]] = \
                summary["by_type"].get(t["type"], 0) + 1
            summary["by_severity"][t["severity"]] = \
                summary["by_severity"].get(t["severity"], 0) + 1
        return summary

    def save_model(self, output_path):
        """Save the threat model as Threat Dragon JSON file."""
        with open(output_path, "w") as f:
            json.dump(self.model, f, indent=2)
        return output_path

    def generate_report(self):
        """Generate threat model assessment report."""
        summary = self.get_threat_summary()
        report = {
            "title": self.model["summary"]["title"],
            "owner": self.model["summary"]["owner"],
            "report_date": datetime.utcnow().isoformat(),
            "diagrams": len(self.model["detail"]["diagrams"]),
            "threat_summary": summary,
            "open_threats": [t for t in self.threats if t["status"] == "Open"],
            "mitigated_threats": [t for t in self.threats
                                  if t["status"] == "Mitigated"],
        }
        print(json.dumps(report, indent=2))
        return report


def main():
    title = sys.argv[1] if len(sys.argv) > 1 else "Sample Application"
    output = sys.argv[2] if len(sys.argv) > 2 else "./threat_model.json"

    agent = ThreatModelAgent(title, owner="Security Team",
                             description="Automated threat model")
    did = agent.add_diagram("Main Data Flow")
    web = agent.add_element(did, "external_entity", "Web Browser", 50, 50)
    api = agent.add_element(did, "process", "API Gateway", 250, 50)
    db = agent.add_element(did, "data_store", "Database", 450, 50)
    flow1 = agent.add_element(did, "data_flow", "HTTPS Request", 150, 100)

    agent.apply_stride(did, web, "external_entity")
    agent.apply_stride(did, api, "process")
    agent.apply_stride(did, db, "data_store")
    agent.apply_stride(did, flow1, "data_flow")

    agent.save_model(output)
    agent.generate_report()


if __name__ == "__main__":
    main()
process.py6.6 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
OWASP Threat Dragon Model Analyzer

Parses Threat Dragon JSON threat model files and generates
summary statistics, coverage reports, and mitigation gap analysis.
"""

import json
import sys
import os
from collections import defaultdict
from datetime import datetime


def load_threat_model(filepath: str) -> dict:
    with open(filepath) as f:
        return json.load(f)


def extract_threats(model: dict) -> list:
    threats = []
    detail = model.get("detail", {})
    for diagram in detail.get("diagrams", []):
        diagram_title = diagram.get("title", "Untitled")
        for cell in diagram.get("cells", []):
            cell_data = cell.get("data", {})
            cell_threats = cell_data.get("threats", [])
            for threat in cell_threats:
                threats.append({
                    "diagram": diagram_title,
                    "element": cell_data.get("name", cell.get("id", "unknown")),
                    "element_type": cell_data.get("type", "unknown"),
                    "title": threat.get("title", ""),
                    "description": threat.get("description", ""),
                    "severity": threat.get("severity", "Unknown"),
                    "status": threat.get("status", "Open"),
                    "type": threat.get("type", ""),
                    "mitigation": threat.get("mitigation", ""),
                    "model_type": threat.get("modelType", "STRIDE"),
                })
    return threats


def analyze_coverage(threats: list) -> dict:
    coverage = {
        "total_threats": len(threats),
        "by_status": defaultdict(int),
        "by_severity": defaultdict(int),
        "by_type": defaultdict(int),
        "by_element_type": defaultdict(int),
        "mitigated_count": 0,
        "open_count": 0,
        "not_applicable_count": 0,
        "with_mitigation_text": 0,
    }

    for threat in threats:
        status = threat["status"]
        coverage["by_status"][status] += 1
        coverage["by_severity"][threat["severity"]] += 1
        coverage["by_type"][threat["type"]] += 1
        coverage["by_element_type"][threat["element_type"]] += 1

        if status.lower() == "mitigated":
            coverage["mitigated_count"] += 1
        elif status.lower() == "open":
            coverage["open_count"] += 1
        elif status.lower() in ("not applicable", "n/a"):
            coverage["not_applicable_count"] += 1

        if threat["mitigation"].strip():
            coverage["with_mitigation_text"] += 1

    coverage["by_status"] = dict(coverage["by_status"])
    coverage["by_severity"] = dict(coverage["by_severity"])
    coverage["by_type"] = dict(coverage["by_type"])
    coverage["by_element_type"] = dict(coverage["by_element_type"])
    return coverage


def identify_gaps(threats: list) -> list:
    gaps = []
    for threat in threats:
        if threat["status"].lower() == "open" and not threat["mitigation"].strip():
            gaps.append({
                "diagram": threat["diagram"],
                "element": threat["element"],
                "threat_title": threat["title"],
                "severity": threat["severity"],
                "type": threat["type"],
            })
    return sorted(gaps, key=lambda g: {"Critical": 0, "High": 1, "Medium": 2, "Low": 3}.get(g["severity"], 4))


def stride_coverage_check(threats: list) -> dict:
    stride_categories = {
        "Spoofing": False,
        "Tampering": False,
        "Repudiation": False,
        "Information disclosure": False,
        "Denial of service": False,
        "Elevation of privilege": False,
    }
    for threat in threats:
        threat_type = threat.get("type", "")
        for category in stride_categories:
            if category.lower() in threat_type.lower():
                stride_categories[category] = True
    return stride_categories


def print_report(model: dict, coverage: dict, gaps: list, stride: dict) -> None:
    summary = model.get("summary", {})
    print(f"\n{'='*60}")
    print(f"Threat Model Analysis Report")
    print(f"{'='*60}")
    print(f"Title: {summary.get('title', 'Unknown')}")
    print(f"Owner: {summary.get('owner', 'Unknown')}")
    print(f"Description: {summary.get('description', '')}")
    print(f"Generated: {datetime.utcnow().isoformat()}Z")

    diagrams = model.get("detail", {}).get("diagrams", [])
    print(f"\nDiagrams: {len(diagrams)}")
    for d in diagrams:
        print(f"  - {d.get('title', 'Untitled')} ({d.get('diagramType', 'Unknown')} type)")

    print(f"\nThreat Summary:")
    print(f"  Total threats: {coverage['total_threats']}")
    print(f"  Mitigated: {coverage['mitigated_count']}")
    print(f"  Open: {coverage['open_count']}")
    print(f"  Not Applicable: {coverage['not_applicable_count']}")
    print(f"  With mitigation documented: {coverage['with_mitigation_text']}")

    if coverage["total_threats"] > 0:
        mitigation_rate = coverage["mitigated_count"] / coverage["total_threats"] * 100
        print(f"  Mitigation rate: {mitigation_rate:.1f}%")

    print(f"\nBy Severity:")
    for sev in ["Critical", "High", "Medium", "Low", "Unknown"]:
        count = coverage["by_severity"].get(sev, 0)
        if count:
            print(f"  {sev:12s}: {count}")

    print(f"\nSTRIDE Coverage:")
    for category, covered in stride.items():
        status = "COVERED" if covered else "MISSING"
        print(f"  {category:25s}: {status}")

    if gaps:
        print(f"\nMitigation Gaps ({len(gaps)} open threats without mitigations):")
        for gap in gaps:
            print(f"  [{gap['severity']}] {gap['threat_title']}")
            print(f"    Element: {gap['element']} | Diagram: {gap['diagram']}")
    else:
        print(f"\nNo mitigation gaps found.")


def main():
    if len(sys.argv) < 2:
        print("Usage: python process.py <threat_model.json>")
        print("  Analyzes an OWASP Threat Dragon JSON threat model file")
        sys.exit(1)

    filepath = sys.argv[1]
    if not os.path.exists(filepath):
        print(f"File not found: {filepath}")
        sys.exit(1)

    model = load_threat_model(filepath)
    threats = extract_threats(model)
    coverage = analyze_coverage(threats)
    gaps = identify_gaps(threats)
    stride = stride_coverage_check(threats)
    print_report(model, coverage, gaps, stride)

    output = filepath.replace(".json", "_analysis.json")
    analysis = {
        "model_title": model.get("summary", {}).get("title"),
        "analysis_date": datetime.utcnow().isoformat() + "Z",
        "coverage": coverage,
        "gaps": gaps,
        "stride_coverage": stride,
    }
    with open(output, "w") as f:
        json.dump(analysis, f, indent=2)
    print(f"\nAnalysis saved to: {output}")


if __name__ == "__main__":
    main()

Assets 1

template.mdtext/markdown · 1.0 KB
Keep exploring