From 5f93d1bc26589e43095c1cc6c9099273205d42d9 Mon Sep 17 00:00:00 2001 From: ellipse2v Date: Tue, 7 Jul 2026 23:06:47 +0200 Subject: [PATCH 1/8] add Red/Blue adversarial debate, gate Graphical Editor, clarify system-model vs threat-model terminology MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add RedBlueDebateEngine: multi-round Red (attack) vs Blue (defence) LLM debate over the top-N GDAF attack scenarios, adjusting path_score/risk_level in place and surfacing a new HTML report section. Opt-in via config/ai_config.yaml (debate.enabled: false by default), degrades silently when AI is offline or no GDAF scenarios exist. - Gate the Graphical Editor behind a new --graphical-editor CLI flag, disabled by default. The /graphical route and its API now 404 and the menu link is hidden unless explicitly enabled. - Rename the DSL title convention from "# Threat Model: {Name}" to "# System Model: {Name}" across docs, prompts, and generated content — the DSL describes the system; SecOpsTM generates the threat model from it. The legacy tag is still parsed with a deprecation warning (get_model_name). Migrate all 30 threatModel_Template/*.md files to the new tag. - Fix two broken "Maintenance" badge URLs and the stale version string in README.md / CLAUDE.md. --- .../skills/secopstm-threat-modeling/SKILL.md | 4 +- .../references/context-format.md | 2 +- .../references/dsl-syntax.md | 16 +- .../references/modeling-strategy.md | 2 +- .../references/naming-conventions.md | 2 +- .../references/output-structure.md | 6 +- .../references/staging-format.md | 2 +- .../references/workflow.md | 4 +- .gitignore | 3 + DOCKER_HUB.md | 4 +- README.md | 13 +- ROADMAP_SUMMARY.md | 4 +- SECURITY.md | 2 +- config/ai_config.yaml | 11 + decisions.md | 8 +- developer.md | 14 +- docs/Roadmap.md | 20 +- docs/claude-code-skill.md | 6 +- docs/customizing_prompts.md | 4 +- docs/data_collection_guide.md | 10 +- docs/defining_threat_models.md | 16 +- docs/dsl-syntax.md | 16 +- docs/examples.md | 12 +- docs/extensibility.md | 2 +- docs/features.md | 4 +- docs/gdaf.md | 2 +- docs/getting_started.md | 4 +- docs/index.md | 26 +- docs/project_onboarding_questionnaire.md | 2 +- .../architecture_overview.md | 18 +- docs/technical_documentation/index.md | 62 +-- docs/threat_modeling_guide.md | 6 +- docs/usage.md | 40 +- docs/workflow.md | 4 +- tests/test_debate_engine.py | 310 +++++++++++++++ tests/test_gdaf_engine.py | 36 ++ tests/test_litellm_provider.py | 14 + tests/test_server.py | 32 +- .../Advanced_Cloud_WebApp/model.md | 2 +- threatModel_Template/CI_CD_Pipeline/model.md | 2 +- .../Cloud_Native_Architecture/model.md | 2 +- .../Data_Warehouse_Data_Lake/model.md | 2 +- .../IP_Camera_Surveillance/model.md | 2 +- threatModel_Template/IoT/model.md | 2 +- .../Kubernetes_Helm_Cluster/model.md | 2 +- .../Microservices_Architecture/model.md | 2 +- .../Mobile_Application/model.md | 2 +- .../On-Prem_Enterprise_Network/model.md | 2 +- .../Satellite_Ground_Segment/model.md | 2 +- .../Serverless_AWS_Lambda/model.md | 2 +- .../model.md | 2 +- .../Three_Tier_Architecture/model.md | 2 +- .../Traditional_Enterprise_Network/model.md | 2 +- .../UAV_Drone_System/model.md | 2 +- threatModel_Template/Zero_Trust/model.md | 2 +- .../projects/example_3/api_gateway/model.md | 2 +- .../example_3/backend/database/model.md | 2 +- .../projects/example_3/backend/model.md | 2 +- .../projects/example_3/dmz/model.md | 2 +- .../projects/example_3/frontend/model.md | 2 +- .../projects/example_3/main.md | 2 +- .../projects/example_3/order_service/model.md | 2 +- .../projects/example_4/card_vault/model.md | 2 +- .../projects/example_4/fraud_engine/model.md | 2 +- .../projects/example_4/main.md | 2 +- .../projects/example_4/payment_api/model.md | 2 +- threatModel_Template/threat_model.md | 2 +- threat_analysis/__main__.py | 14 +- .../ai_engine/providers/base_provider.py | 23 ++ .../ai_engine/providers/litellm_provider.py | 16 + threat_analysis/config/prompts.yaml | 108 ++++- threat_analysis/core/debate_engine.py | 370 ++++++++++++++++++ threat_analysis/core/gdaf_engine.py | 31 +- .../generation/report_generator.py | 108 +++++ threat_analysis/server/ai_service.py | 2 +- threat_analysis/server/server.py | 34 +- .../server/static/js/ThreatModelGenerator.js | 4 +- threat_analysis/server/static/js/config.js | 2 +- threat_analysis/server/templates/index.html | 2 + .../server/templates/simple_mode.html | 6 +- .../templates/report_template.html | 133 ++++++- 81 files changed, 1429 insertions(+), 225 deletions(-) create mode 100644 tests/test_debate_engine.py create mode 100644 threat_analysis/core/debate_engine.py diff --git a/.claude/skills/secopstm-threat-modeling/SKILL.md b/.claude/skills/secopstm-threat-modeling/SKILL.md index e1de3d1..df0b93f 100644 --- a/.claude/skills/secopstm-threat-modeling/SKILL.md +++ b/.claude/skills/secopstm-threat-modeling/SKILL.md @@ -25,7 +25,7 @@ description: Perform comprehensive threat modeling for target systems using SecO ``` {system-name}/ -├── model.md # Threat model +├── model.md # System model ├── context/ │ └── {system-name}_context.yaml ├── BOM/ @@ -129,7 +129,7 @@ Output modeling strategy for user confirmation: system name, complexity (high/me - "Drone 350" → `drone-350` **Filename Consistency**: -- model.md title: `# Threat Model: DJI Power Inspection` +- model.md title: `# System Model: DJI Power Inspection` - Context file: `context/dji-power-inspection_context.yaml` - Ensure consistent use of hyphens/underscores diff --git a/.claude/skills/secopstm-threat-modeling/references/context-format.md b/.claude/skills/secopstm-threat-modeling/references/context-format.md index adeac86..7e1c054 100644 --- a/.claude/skills/secopstm-threat-modeling/references/context-format.md +++ b/.claude/skills/secopstm-threat-modeling/references/context-format.md @@ -2,7 +2,7 @@ ## Overview -The GDAF (Generic Threat Modeling and Attack Framework) context file defines the attack context for threat modeling. It includes attack objectives, threat actors, and risk criteria. +The GDAF (Goal-Driven Attack Flow) context file defines the attack context used for attack path analysis. It includes attack objectives, threat actors, and risk criteria. ## File Location diff --git a/.claude/skills/secopstm-threat-modeling/references/dsl-syntax.md b/.claude/skills/secopstm-threat-modeling/references/dsl-syntax.md index 45bd5cc..d295b1a 100644 --- a/.claude/skills/secopstm-threat-modeling/references/dsl-syntax.md +++ b/.claude/skills/secopstm-threat-modeling/references/dsl-syntax.md @@ -18,7 +18,7 @@ ## Overview -SecOpsTM threat model is a Markdown file parsed in three passes: +A SecOpsTM system model is a Markdown file parsed in three passes: 1. **Pass 0** — `## Context` (model-level configuration) 2. **First pass** — `## Boundaries`, `## Actors`, `## Servers`, `## Data` (element definitions) @@ -40,14 +40,16 @@ SecOpsTM threat model is a Markdown file parsed in three passes: **⚠️ CRITICAL**: model.md must use the following title format: ```markdown -# Threat Model: {System Name} +# System Model: {System Name} ``` **Format requirements**: -- Must start with `# Threat Model: ` (English colon `:` + space) +- Must start with `# System Model: ` (English colon `:` + space) - System name follows after the space -- **Correct**: `# Threat Model: DJI Power Inspection Drone System` -- **Incorrect**: `# DJI Power Inspection Drone System - Threat Model` +- **Correct**: `# System Model: DJI Power Inspection Drone System` +- **Incorrect**: `# DJI Power Inspection Drone System - System Model` +- The legacy `# Threat Model: {System Name}` form is still read correctly (with a + deprecation warning) — always write new models with `# System Model:`. **Impact**: Incorrect title format causes SecOpsTM to fail generating report filenames correctly. @@ -163,7 +165,7 @@ Actors represent people, external systems, or roles interacting with the system, ## Section: Servers -Servers represent assets, components, or systems in the threat model. +Servers represent assets, components, or systems in the system model. ### Server Attributes @@ -482,7 +484,7 @@ Use `config/asset_types_community.yaml` and `config/protocols_community.yaml` to **⚠️ CRITICAL FORMAT REQUIREMENT**: SecOpsTM parser requires EXACT DSL syntax. DO NOT use custom Markdown formats like `### Boundary:` or `**Name**:`. Use ONLY the list format shown below. ```markdown -# Threat Model: {System English Name} +# System Model: {System English Name} ## Description {High-level system description in English} diff --git a/.claude/skills/secopstm-threat-modeling/references/modeling-strategy.md b/.claude/skills/secopstm-threat-modeling/references/modeling-strategy.md index 8022196..5f73736 100644 --- a/.claude/skills/secopstm-threat-modeling/references/modeling-strategy.md +++ b/.claude/skills/secopstm-threat-modeling/references/modeling-strategy.md @@ -41,7 +41,7 @@ Use single-model when **ALL** of the following are true: ``` {system-name}/ -├── model.md # Single threat model +├── model.md # Single system model ├── context/ │ └── {system-name}_context.yaml ├── BOM/ diff --git a/.claude/skills/secopstm-threat-modeling/references/naming-conventions.md b/.claude/skills/secopstm-threat-modeling/references/naming-conventions.md index 7535bbd..cafee26 100644 --- a/.claude/skills/secopstm-threat-modeling/references/naming-conventions.md +++ b/.claude/skills/secopstm-threat-modeling/references/naming-conventions.md @@ -2,7 +2,7 @@ ## Overview -Proper naming conventions ensure consistency and readability in threat models. +Proper naming conventions ensure consistency and readability in system models. ## Asset Names diff --git a/.claude/skills/secopstm-threat-modeling/references/output-structure.md b/.claude/skills/secopstm-threat-modeling/references/output-structure.md index d5fd1bd..3469fe2 100644 --- a/.claude/skills/secopstm-threat-modeling/references/output-structure.md +++ b/.claude/skills/secopstm-threat-modeling/references/output-structure.md @@ -8,7 +8,7 @@ This document describes the output structure for SecOpsTM threat modeling. ``` {system-name}/ -├── model.md # Threat model +├── model.md # System model ├── context/ │ └── {system-name}_context.yaml ├── BOM/ @@ -22,7 +22,7 @@ This document describes the output structure for SecOpsTM threat modeling. | File | Required | Description | |------|----------|-------------| -| `model.md` | ✅ | Main threat model file in SecOpsTM DSL format | +| `model.md` | ✅ | Main system model file in SecOpsTM DSL format | | `context/{system-name}_context.yaml` | ✅ | GDAF attack context (objectives, threat actors, risk criteria) | | `BOM/{asset_name}.yaml` | ✅ | Bill of Materials - one file per key asset | | `config/asset_types_community.yaml` | ❌ | Custom asset type definitions (if needed) | @@ -56,7 +56,7 @@ This document describes the output structure for SecOpsTM threat modeling. |------|----------|-------------| | `main.md` | ✅ | Top-level architecture showing subsystem boundaries and data flows | | `context/{project-name}_context.yaml` | ✅ | Global GDAF context for the entire project | -| `{subsystem}/model.md` | ✅ | Subsystem-specific threat model | +| `{subsystem}/model.md` | ✅ | Subsystem-specific system model | | `{subsystem}/BOM/{asset}.yaml` | ✅ | Subsystem-specific BOM files | | `config/*` | ❌ | Custom asset types and protocols (shared across subsystems) | diff --git a/.claude/skills/secopstm-threat-modeling/references/staging-format.md b/.claude/skills/secopstm-threat-modeling/references/staging-format.md index d46e8cc..9fced72 100644 --- a/.claude/skills/secopstm-threat-modeling/references/staging-format.md +++ b/.claude/skills/secopstm-threat-modeling/references/staging-format.md @@ -2,7 +2,7 @@ ## Overview -Before generating the threat model, collect and structure information in the `{project-name}/.tm-raw/` directory. This enables incremental collection, source tracking, and conflict resolution. +Before generating the system model, collect and structure information in the `{project-name}/.tm-raw/` directory. This enables incremental collection, source tracking, and conflict resolution. ## Directory Structure diff --git a/.claude/skills/secopstm-threat-modeling/references/workflow.md b/.claude/skills/secopstm-threat-modeling/references/workflow.md index b732e03..37edff1 100644 --- a/.claude/skills/secopstm-threat-modeling/references/workflow.md +++ b/.claude/skills/secopstm-threat-modeling/references/workflow.md @@ -266,11 +266,11 @@ After information gathering and complexity assessment, **present the modeling st - Rationale: {Why this decomposition} Please confirm if the modeling strategy is appropriate: -1. Confirm - Generate threat model with this strategy +1. Confirm - Generate system model with this strategy 2. Adjust - User provides modification suggestions (e.g., merge some subsystems/change to single-model) ``` -**After user confirmation**, then generate the specific threat model files. +**After user confirmation**, then generate the specific system model files. --- ### Step 6: Generate Model File diff --git a/.gitignore b/.gitignore index 4ec6900..53e1bb3 100644 --- a/.gitignore +++ b/.gitignore @@ -215,3 +215,6 @@ external_data.tar.gz # Git worktrees .worktrees/ + +# Subagent-driven-development scratch workspace (briefs, diffs, ledger) +.superpowers/ diff --git a/DOCKER_HUB.md b/DOCKER_HUB.md index 9291e9e..d05cac6 100644 --- a/DOCKER_HUB.md +++ b/DOCKER_HUB.md @@ -16,7 +16,7 @@ docker run -p 5000:5000 \ ellipse2v/secopstm:latest ``` -Open **http://localhost:5000**, paste your threat model and click **Generate**. +Open **http://localhost:5000**, paste your system model and click **Generate**. Reports land in `$(pwd)/output//`. --- @@ -65,7 +65,7 @@ The named volume `secopstm-rag` persists across restarts and image upgrades. | What | Docker flag | Notes | |---|---|---| | Output reports | `-v $(pwd)/output:/app/output` | Files land in `output//` on the host | -| Threat model files | `-v $(pwd)/models:/models` | Then pass `--model-file /models/model.md` | +| System model files | `-v $(pwd)/models:/models` | Then pass `--model-file /models/model.md` | | AI config | `-v $(pwd)/ai_config.yaml:/app/config/ai_config.yaml:ro` | Change provider or model without rebuilding | | LLM prompts | `-v $(pwd)/prompts.yaml:/app/config/prompts.yaml:ro` | Override system prompts | | CVE definitions | `-v $(pwd)/cve_definitions.yml:/app/cve_definitions.yml:ro` | Per-asset CVE list | diff --git a/README.md b/README.md index 3811a44..600aab8 100644 --- a/README.md +++ b/README.md @@ -23,13 +23,13 @@ This project is a Python-based, end-to-end STRIDE threat modeling and analysis f [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) -[![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/yourusername/cybersec-mapping/graphs/commit-activity) +[![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/ellipse2v/SecOpsTM/graphs/commit-activity) --- ## ✨ New Interactive Features -The framework's web interface has been significantly updated to provide a more powerful and seamless threat modeling experience. +The web interface now supports the following. ### Interactive Diagrams The generated diagrams (both in the live editor and in exported HTML reports) are not static images. They are fully interactive SVGs that allow you to: @@ -40,13 +40,12 @@ The generated diagrams (both in the live editor and in exported HTML reports) ar ### Interactive Legend - **Filter Connections**: The diagram legend is now interactive. Click on a protocol (e.g., HTTPS, TCP) to instantly show or hide all dataflows using that protocol, making it easy to analyze specific parts of your data flow. -### Robust Project Generation -- The **"Generate All"** feature is designed to handle complex projects with nested sub-models. -- If you have a project with multiple threat model files, it intelligently detects if any referenced sub-models are not currently open in the editor and prompts you to select your project's root directory. This ensures that all necessary files are found and included for a complete and accurate generation of all reports and diagrams. +### Project Generation +- The **"Generate All"** feature handles projects with nested sub-models. +- If you have a project with multiple system model files, it detects when a referenced sub-model is not currently open in the editor and prompts you to select your project's root directory, so all files are found before generating reports and diagrams. ### Simple Server Mode -- The integrated web server can be started with a path to a project directory (`--project path/to/your/project`). It will automatically find all `*.md` threat model files within that project and open them in tabs, ready for editing. -[![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/ellipse2v/cybersec-mapping/graphs/commit-activity) +- The integrated web server can be started with a path to a project directory (`--project path/to/your/project`). It automatically finds all `*.md` system model files within that project and opens them in tabs, ready for editing. --- diff --git a/ROADMAP_SUMMARY.md b/ROADMAP_SUMMARY.md index 52961c3..7434686 100644 --- a/ROADMAP_SUMMARY.md +++ b/ROADMAP_SUMMARY.md @@ -3,9 +3,9 @@ This roadmap outlines the SecOpsTM framework's current capabilities and future plans. ### Implemented Features: -- **Hierarchical & Interactive Threat Models**: Support for linked sub-models and navigable diagrams. +- **Hierarchical & Interactive System Models**: Support for linked sub-models and navigable diagrams. - **Automated Mitigations**: Context-aware suggestions from OWASP ASVS, NIST, and CIS Controls. -- **Ansible IaC Integration**: Automatic threat model generation from Ansible playbooks. +- **Ansible IaC Integration**: Automatic system model generation from Ansible playbooks. - **Web-Based GUI**: Interactive web interface for real-time model editing and preview. - **MITRE ATT&CK Integration**: Export to Navigator and STIX 2.1 formats for visualization and threat intelligence. diff --git a/SECURITY.md b/SECURITY.md index e21f508..caa8eaa 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -39,7 +39,7 @@ - Remote code execution via the web server (`--server` mode) - Unauthorized access to the filesystem through the API - - Injection vulnerabilities (YAML, template, command) in threat model parsing + - Injection vulnerabilities (YAML, template, command) in system model parsing - Authentication bypass (if authentication is added in future versions) The following are **out of scope**: diff --git a/config/ai_config.yaml b/config/ai_config.yaml index c279da5..4d78e22 100644 --- a/config/ai_config.yaml +++ b/config/ai_config.yaml @@ -165,6 +165,17 @@ gdaf: max_paths_per_objective: 3 gdaf_min_technique_score: 0.8 +# Red/Blue adversarial debate over the top-N GDAF attack scenarios (opt-in). +# See docs/gdaf.md. Runs after GDAF and before the HTML report is written. +debate: + enabled: false + top_n: 5 + min_viability_threshold: 0.5 + max_rounds: 3 + viability_delta_threshold: 0.1 + debate_factor_min: 0.5 + debate_factor_max: 1.5 + # Fine-tuning (Phase 3 — optional, not enabled by default) fine_tuning: enabled: false diff --git a/decisions.md b/decisions.md index 3593ca2..7b3e4e4 100644 --- a/decisions.md +++ b/decisions.md @@ -133,7 +133,7 @@ forking all downstream threat processing logic. CVE JSONL, NIST XLSX) are separate one-shot tools, not part of the runtime application. **Why it matters:** The transformed artifacts (`external_data/*.json`, `vector_store/`) are committed -to the repo so end users do not need network access or tool dependencies to use the threat model. +to the repo so end users do not need network access or tool dependencies to use the tool. The pipeline only needs to run when upstream data sources update. **Trade-off accepted:** The repo is large (CVE JSONL files from 1999–2025 are significant). @@ -329,7 +329,7 @@ crossing them). Adding boundaries as AI targets surfaces zone-level threats that misses. **What was rejected:** Generating boundary threats as a separate post-processing step (e.g. from -the ATT&CK mapping alone) was considered but would not leverage the contextual knowledge of the +the ATT&CK mapping alone) was considered but would not use the contextual knowledge of the LLM about the specific architecture described in the model. --- @@ -399,13 +399,13 @@ that cannot be inferred from the architecture model alone. ## Decision: Sub-model drill-down — child IS the parent at higher detail -**Choice:** A server in a threat model can reference a child model via `submodel=./path/to/model.md`. +**Choice:** A server in a system model can reference a child model via `submodel=./path/to/model.md`. The child is not a separate dependency or a called service — it IS the same component described at a finer granularity. The parent diagram links to the child; the child diagram shows ghost nodes for external connections from the parent. **Why it matters:** Large architectures become unreadable when everything is in one flat diagram. -Drill-down allows a threat model to be decomposed: the top-level model shows the overall +Drill-down allows a system model to be decomposed: the top-level model shows the overall architecture with summarized components, and each component that merits deeper analysis gets its own model file with the same full DSL support (actors, servers, dataflows, STRIDE analysis, AI enrichment). diff --git a/developer.md b/developer.md index 09a5067..6c49b7f 100644 --- a/developer.md +++ b/developer.md @@ -48,13 +48,13 @@ This will download the necessary file to `threat_analysis/external_data/`. ## Future Development: Infrastructure as Code (IaC) Integration -This section outlines the planned integration of Infrastructure as Code (IaC) tools, starting with Ansible, into the STRIDE Threat Analysis Framework. The goal is to enhance the "Threat Modeling as Code" philosophy by enabling automated threat model generation and analysis directly from IaC configurations. +This section outlines the planned integration of Infrastructure as Code (IaC) tools, starting with Ansible, into the STRIDE Threat Analysis Framework. The goal is to enhance the "Threat Modeling as Code" philosophy by enabling automated system model generation and threat analysis directly from IaC configurations. ### Vision -The core idea is to bridge the gap between infrastructure definitions (e.g., Ansible playbooks, Terraform configurations) and the threat model. By parsing IaC files, we can automatically infer system components, data flows, and configurations, and then use this information to: +The core idea is to bridge the gap between infrastructure definitions (e.g., Ansible playbooks, Terraform configurations) and the system model. By parsing IaC files, we can automatically infer system components, data flows, and configurations, and then use this information to: -1. **Dynamically Generate/Update Threat Models**: Automatically create or update `threatModel_Template/threat_model.md` based on the deployed infrastructure. +1. **Dynamically Generate/Update System Models**: Automatically create or update `threatModel_Template/threat_model.md` based on the deployed infrastructure. 2. **Automate Threat Detection**: Identify potential STRIDE threats and map them to MITRE ATT&CK techniques based on the IaC configuration (e.g., open ports, insecure configurations). 3. **Detect New Threats**: Compare threat analysis reports before and after IaC changes to highlight newly introduced threats or changes in the attack surface. @@ -65,7 +65,7 @@ This initiative directly supports the "Threat Modeling as Code" workflow detaile The integration will involve the following steps: 1. **IaC Parser Development**: Create dedicated modules to read and interpret IaC configuration files (e.g., Ansible playbooks, Terraform `.tf` files). These parsers will extract relevant infrastructure details (e.g., servers, network configurations, deployed services). -2. **Mapping to Threat Model DSL**: The extracted IaC data will be translated into the components of our Markdown-based Threat Model DSL (Boundaries, Actors, Servers, Data, Dataflows). +2. **Mapping to System Model DSL**: The extracted IaC data will be translated into the components of our Markdown-based System Model DSL (Boundaries, Actors, Servers, Data, Dataflows). 3. **Dynamic `threatModel_Template/threat_model.md` Generation**: The translated data will be used to generate or update the `threatModel_Template/threat_model.md` file programmatically. 4. **Automated Analysis & Reporting**: The existing threat analysis framework (`threat_analysis/__main__.py`) will then process the generated `threatModel_Template/threat_model.md` to perform STRIDE analysis, MITRE mapping, and report generation. 5. **Change Detection (Future)**: Mechanisms will be explored to compare analysis results over time, identifying new or mitigated threats resulting from IaC changes. @@ -75,12 +75,12 @@ The integration will involve the following steps: We will begin by integrating with Ansible. This phase will focus on: 1. **Ansible Playbook/Inventory Parser**: A module will be developed to parse Ansible playbooks and inventory files to identify hosts, roles, tasks, and network configurations. -2. **Mapping Logic**: Define clear rules for how Ansible constructs (e.g., hosts, tasks that open ports, roles that install services) map to threat model elements. -3. **CLI Integration**: A new command-line option (e.g., `--from-ansible `) will be added to `threat_analysis/__main__.py` to trigger the Ansible-based threat model generation. +2. **Mapping Logic**: Define clear rules for how Ansible constructs (e.g., hosts, tasks that open ports, roles that install services) map to system model elements. +3. **CLI Integration**: A new command-line option (e.g., `--from-ansible `) will be added to `threat_analysis/__main__.py` to trigger the Ansible-based system model generation. #### Test Playbook -To facilitate development and testing, a sample Ansible playbook will be stored within the project. This playbook will define a simple infrastructure that can be used to validate the parsing and threat model generation logic. +To facilitate development and testing, a sample Ansible playbook will be stored within the project. This playbook will define a simple infrastructure that can be used to validate the parsing and system model generation logic. **Location**: `tests/ansible_playbooks/simple_web_server.yml` (or similar) diff --git a/docs/Roadmap.md b/docs/Roadmap.md index b1c4784..7b57801 100644 --- a/docs/Roadmap.md +++ b/docs/Roadmap.md @@ -8,12 +8,12 @@ This document outlines the development roadmap for the SecOpsTM framework, track This section highlights the core capabilities that are already integrated into the framework. -- **Hierarchical & Interactive Threat Models**: Decompose a large system into multiple, linked sub-models with navigable HTML diagrams. -- **Advanced Threat Model Validation**: A dedicated validation module (`model_validator.py`) checks for consistency, unique names, and valid references within the model before analysis. +- **Hierarchical & Interactive System Models**: Decompose a large system into multiple, linked sub-models with navigable HTML diagrams. +- **Advanced System Model Validation**: A dedicated validation module (`model_validator.py`) checks for consistency, unique names, and valid references within the model before analysis. - **Automated and Enriched Mitigation Suggestions**: The framework proposes context-aware mitigations based on recognized frameworks (OWASP ASVS, NIST, CIS Controls) for each identified MITRE ATT&CK technique. -- **Ansible IaC Integration**: Automatically generate a threat model directly from Ansible playbooks and inventories, using embedded metadata for a rich, accurate representation. -- **Pre-defined Architecture Templates**: A library of pre-built threat models for common architectural patterns is available in the `threatModel_Template/` directory to accelerate initial setup. -- **Web-Based GUI**: An interactive web interface for real-time editing of threat models with a live-updating diagram preview. +- **Ansible IaC Integration**: Automatically generate a system model directly from Ansible playbooks and inventories, using embedded metadata for a rich, accurate representation. +- **Pre-defined Architecture Templates**: A library of pre-built system models for common architectural patterns is available in the `threatModel_Template/` directory to speed up initial setup. +- **Web-Based GUI**: An interactive web interface for real-time editing of system models with a live-updating diagram preview. - **MITRE ATT&CK Navigator Export**: Generate JSON layer files for visualization and analysis in the MITRE ATT&CK Navigator. - **STIX 2.1 Reporting**: Export threat intelligence data in the standardized STIX 2.1 format for interoperability with other security tools. @@ -24,16 +24,16 @@ This section highlights the core capabilities that are already integrated into t This section outlines the strategic vision and planned features for future releases. - **Integration with Vulnerability Databases (CVE)**: Link identified MITRE ATT&CK techniques to known CVEs or common vulnerabilities (e.g., OWASP Top 10) to provide even deeper context. -- **Attack Path Enumeration and Simulation**: Implement algorithms to automatically identify and visualize potential attack paths through the threat model. +- **Attack Path Enumeration and Simulation**: Implement algorithms to automatically identify and visualize potential attack paths through the system model. - **Integration with Security Orchestration, Automation, and Response (SOAR) Platforms**: Develop connectors to push threat intelligence and mitigation recommendations directly into SOAR platforms. - **Machine Learning-Enhanced Threat Identification**: Train a machine learning model to predict potential threats that may not be covered by existing rules. - **Expanded IaC Tool Support (Terraform, CloudFormation)**: Create new plugins for other popular IaC tools. - **Enhanced User Interface (UI) and User Experience (UX)**: Redesign the web GUI to be more intuitive, with features like drag-and-drop model creation and a visual rule editor. - **Compliance Mapping (NIST, PCI-DSS, etc.)**: Map identified threats and suggested mitigations to specific controls in major compliance frameworks. - **Threat Intelligence Feed Integration**: Integrate with external threat intelligence feeds (e.g., from MISP, Anomali) to automatically update the threat model with the latest real-world attack techniques. -- **Collaborative Real-Time Editing**: Enhance the web GUI to support real-time, multi-user collaborative editing of threat models. +- **Collaborative Real-Time Editing**: Enhance the web GUI to support real-time, multi-user collaborative editing of system models. - **Risk Quantification and Financial Impact Analysis**: Integrate with risk quantification models (e.g., FAIR) to estimate the potential financial impact of identified threats. -- **Automated Attack Path Analysis**: Generate potential attack paths by chaining together identified MITRE ATT&CK techniques across all nodes in the threat model. This will involve enriching the CAPEC to ATT&CK mappings to build a comprehensive attack graph. +- **Automated Attack Path Analysis**: Generate potential attack paths by chaining together identified MITRE ATT&CK techniques across all nodes in the system model. This will involve enriching the CAPEC to ATT&CK mappings to build a full attack graph. - **Advanced Severity Multiplier Calculation**: Implement a more granular and qualitative method for calculating the severity multiplier of each asset. This will be based on a rating of four key security criteria: * **Confidentiality**: The impact of unauthorized disclosure of data. * **Integrity**: The impact of unauthorized modification of data. @@ -48,8 +48,8 @@ This section outlines the strategic vision and planned features for future relea 2. **Dataset Creation**: Build a high-quality dataset for fine-tuning, including: - The complete knowledge bases of **MITRE ATT&CK, CAPEC, and D3FEND**. - Thousands of public **threat intelligence reports** (from CISA, Mandiant, etc.) and **CVE** descriptions. - - All existing **threat models (`.md` files)** from the project, so the LLM can learn the DSL and architectural patterns. - - Pairs of "input threat model" -> "output list of threats and attack paths" generated by the current tool for supervised learning. + - All existing **system models (`.md` files)** from the project, so the LLM can learn the DSL and architectural patterns. + - Pairs of "input system model" -> "output list of threats and attack paths" generated by the current tool for supervised learning. 3. **Training**: Fine-tune the base model on this dataset to specialize it in the "thinking" of a security expert. * **Usage Phase (Inference)**: 1. **Hybrid Approach**: The current rule engine remains the baseline for fast, deterministic analysis. The LLM acts as a second pass. diff --git a/docs/claude-code-skill.md b/docs/claude-code-skill.md index 8bfb032..bccada8 100644 --- a/docs/claude-code-skill.md +++ b/docs/claude-code-skill.md @@ -1,4 +1,4 @@ -# Claude Code Skill — AI-Assisted Threat Model Generation +# Claude Code Skill — AI-Assisted System Model Generation SecOpsTM ships a **Claude Code skill** that guides an AI agent through the full threat modeling workflow: information gathering, DSL file generation, BOM/context YAML generation, and validation. @@ -159,7 +159,7 @@ after the initial description. ### Step 6 — model.md Generation -Claude writes the threat model DSL file. All output is in English regardless of the language +Claude writes the system model DSL file. All output is in English regardless of the language used during information gathering. Key rules enforced automatically: @@ -230,7 +230,7 @@ Target: **0 failures**. Claude fixes any failures and re-runs before reporting c ``` {system-name}/ ├── .tm-raw/ # scratch files (not committed) -├── model.md # threat model DSL +├── model.md # system model DSL ├── context/ │ └── {system-name}_context.yaml # GDAF objectives, actors, risk criteria ├── BOM/ diff --git a/docs/customizing_prompts.md b/docs/customizing_prompts.md index 99649ec..a3eac9f 100644 --- a/docs/customizing_prompts.md +++ b/docs/customizing_prompts.md @@ -51,7 +51,7 @@ converts it into the SecOpsTM Markdown DSL. dsl_generation: system: | You are an expert cybersecurity architect for a payment-processing organisation. - Generate threat models in the SecOpsTM Markdown DSL. + Generate system models in the SecOpsTM Markdown DSL. Naming conventions: - Boundaries: use zone names (e.g. "DMZ", "CardholderDataEnvironment", "Management") @@ -311,7 +311,7 @@ rag: ## Threat Intelligence {user_threat_intelligence} - ## Architecture (Threat Model) + ## Architecture (System Model) {threat_model_markdown} ## Retrieved Security Knowledge diff --git a/docs/data_collection_guide.md b/docs/data_collection_guide.md index fede11d..2b315c7 100644 --- a/docs/data_collection_guide.md +++ b/docs/data_collection_guide.md @@ -1,11 +1,11 @@ # Data Collection Guide for Threat Modeling -This guide tells you exactly what information to gather before building a SecOpsTM threat model. +This guide tells you exactly what information to gather before building a SecOpsTM system model. It is written for security architects and DevOps engineers who are conducting or commissioning a threat modeling session. You can hand this document to a system owner and expect to receive back everything the tool needs. -A well-filled threat model produces: a STRIDE threat report with MITRE ATT&CK mappings, a +A well-filled system model produces: a STRIDE threat report with MITRE ATT&CK mappings, a severity-colored architecture diagram, goal-driven attack scenarios (GDAF), and exports to STIX 2.1 and ATT&CK Navigator. @@ -26,7 +26,7 @@ SecOpsTM models a system by describing: 6. **What the system inventory looks like** (BOM) 7. **What attackers want** and **who they are** (GDAF context) -Each piece of information maps directly to a section in the threat model file. Missing information +Each piece of information maps directly to a section in the system model file. Missing information produces lower-quality threat coverage — for example, a server without CIA ratings gets no GDAF path scoring, and a dataflow without `is_encrypted` gets a default of `False` which adds threat noise. @@ -557,7 +557,7 @@ build the DSL file. **Minimal viable model template to fill in:** ```markdown -# Threat Model: [SYSTEM NAME] +# System Model: [SYSTEM NAME] ## Description [2–5 sentences describing the system, its purpose, user count, deployment, compliance scope] @@ -627,7 +627,7 @@ A small e-commerce application with a public API, an internal database, and an a **Resulting model.md:** ```markdown -# Threat Model: E-Commerce Platform +# System Model: E-Commerce Platform ## Description Customer-facing e-commerce API built on Node.js and PostgreSQL. Approximately 50,000 customers diff --git a/docs/defining_threat_models.md b/docs/defining_threat_models.md index 41cfeed..8d663aa 100644 --- a/docs/defining_threat_models.md +++ b/docs/defining_threat_models.md @@ -2,7 +2,7 @@ This document is the authoritative reference for the SecOpsTM Markdown Domain-Specific Language (DSL). It covers every section, every attribute, and every accepted value. After reading this, you -can write any threat model from scratch without consulting the source code. +can write any system model from scratch without consulting the source code. **Target audience:** DevOps engineers, security architects, threat modelers. @@ -10,7 +10,8 @@ can write any threat model from scratch without consulting the source code. ## Overview -A SecOpsTM threat model is a Markdown file. The file is parsed in three passes: +A SecOpsTM system model is a Markdown file describing the architecture to analyze; SecOpsTM +generates the threat model from it. The file is parsed in three passes: 1. **Pass 0** — `## Context` (standalone key-value block, model-level settings) 2. **First pass** — `## Boundaries`, `## Actors`, `## Servers`, `## Data` (element definitions) @@ -36,7 +37,7 @@ This order matters: dataflows can only reference elements that were defined in t ## File Structure ```markdown -# Threat Model: My System Name +# System Model: My System Name ## Description Free-form text describing the system under analysis. @@ -70,8 +71,9 @@ bom_directory = BOM - **Attack Name**: {"tactics": ["Lateral Movement"], "techniques": [{"id": "T1021.002", "name": "SMB/Windows Admin Shares"}]} ``` -The title (`# Threat Model: ...`) is used as the model name in reports and diagrams. Everything -else is controlled by the sections described below. +The title (`# System Model: ...`) is used as the model name in reports and diagrams. Everything +else is controlled by the sections described below. The legacy `# Threat Model: ...` form still +works (with a deprecation warning) — always write new models with `# System Model:`. --- @@ -666,7 +668,7 @@ The recommended structure for a project-mode model (with GDAF context, BOM, and ``` My_System/ - model.md # Main threat model DSL file + model.md # Main system model DSL file context/ # GDAF context YAML (auto-discovered) context.yaml BOM/ # Per-asset Bill of Materials (auto-discovered) @@ -994,7 +996,7 @@ features. It models a large enterprise with 9 security zones, Active Directory, OT/SCADA. ```markdown -# Threat Model: On-Prem Enterprise Network +# System Model: On-Prem Enterprise Network ## Description Large multinational manufacturing enterprise with on-premises Active Directory infrastructure, diff --git a/docs/dsl-syntax.md b/docs/dsl-syntax.md index da28666..e80a79f 100644 --- a/docs/dsl-syntax.md +++ b/docs/dsl-syntax.md @@ -18,7 +18,7 @@ ## Overview -SecOpsTM threat model is a Markdown file parsed in three passes: +A SecOpsTM system model is a Markdown file parsed in three passes: 1. **Pass 0** — `## Context` (model-level configuration) 2. **First pass** — `## Boundaries`, `## Actors`, `## Servers`, `## Data` (element definitions) @@ -40,14 +40,16 @@ SecOpsTM threat model is a Markdown file parsed in three passes: **⚠️ CRITICAL**: model.md must use the following title format: ```markdown -# Threat Model: {System Name} +# System Model: {System Name} ``` **Format requirements**: -- Must start with `# Threat Model: ` (English colon `:` + space) +- Must start with `# System Model: ` (English colon `:` + space) - System name follows after the space -- **Correct**: `# Threat Model: DJI Power Inspection Drone System` -- **Incorrect**: `# DJI Power Inspection Drone System - Threat Model` +- **Correct**: `# System Model: DJI Power Inspection Drone System` +- **Incorrect**: `# DJI Power Inspection Drone System - System Model` +- The legacy `# Threat Model: {System Name}` form still works (with a deprecation warning) — + always write new models with `# System Model:`. **Impact**: Incorrect title format causes SecOpsTM to fail generating report filenames correctly. @@ -163,7 +165,7 @@ Actors represent people, external systems, or roles interacting with the system, ## Section: Servers -Servers represent assets, components, or systems in the threat model. +Servers represent assets, components, or systems in the system model. ### Server Attributes @@ -482,7 +484,7 @@ Use `config/asset_types_community.yaml` and `config/protocols_community.yaml` to **⚠️ CRITICAL FORMAT REQUIREMENT**: SecOpsTM parser requires EXACT DSL syntax. DO NOT use custom Markdown formats like `### Boundary:` or `**Name**:`. Use ONLY the list format shown below. ```markdown -# Threat Model: {System English Name} +# System Model: {System English Name} ## Description {High-level system description in English} diff --git a/docs/examples.md b/docs/examples.md index 7bdf3af..a1e4b1a 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -1,9 +1,9 @@ -# Threat Model DSL & Examples +# System Model DSL & Examples -This framework leverages PyTM's modeling primitives. For a comprehensive reference of all available attributes and their usage, please refer to the [PyTM Documentation](https://owasp.github.io/PyTM/Threat-Model-as-Code/). Note that this framework may extend PyTM with additional attributes or behaviors. +This framework uses PyTM's modeling primitives. For a full reference of all available attributes and their usage, see the [PyTM Documentation](https://owasp.github.io/PyTM/Threat-Model-as-Code/). This framework may extend PyTM with additional attributes or behaviors. ```markdown -# Threat Model: Advanced DMZ Architecture +# System Model: Advanced DMZ Architecture ## Description A network with a DMZ, external/internal firewalls, and a command zone. The goal is to identify STRIDE threats and map them to MITRE ATT&CK. @@ -39,7 +39,7 @@ A network with a DMZ, external/internal firewalls, and a command zone. The goal ## Common Attributes -Several elements in the threat model DSL support common attributes to enhance their representation and behavior: +Several elements in the system model DSL support common attributes that affect their representation and behavior: - **`color`**: Specifies the color of the element in the generated diagrams. - **Applies to**: Boundaries, Actors, Dataflows, Protocol Styles. @@ -59,7 +59,7 @@ Several elements in the threat model DSL support common attributes to enhance th ## Protocol Styles and Legends -To ensure that protocols are correctly styled in diagrams and appear in the legend, you must define them in the `## Protocol Styles` section of your threat model. The system **intentionally does not** assign default colors to new protocols. This gives you full control over the final visualization. +To ensure that protocols are correctly styled in diagrams and appear in the legend, you must define them in the `## Protocol Styles` section of your system model. The system **intentionally does not** assign default colors to new protocols. This gives you full control over the final visualization. **How it works:** @@ -212,6 +212,6 @@ After running the analysis, you will find a timestamped folder in `output/` (e.g ### Using Pre-defined Templates -To accelerate the creation of new threat models, the framework includes a set of pre-defined templates for common architectures. You can load these templates directly from the web interface. +To speed up the creation of new system models, the framework includes a set of pre-defined templates for common architectures. You can load these templates directly from the web interface. ![Loading a template](../../output/example/gui_example.gif) diff --git a/docs/extensibility.md b/docs/extensibility.md index 111231d..dac075f 100644 --- a/docs/extensibility.md +++ b/docs/extensibility.md @@ -29,7 +29,7 @@ ## PyTM-Based Extensions & Evolutions -You can leverage and extend all PyTM features, including: +You can use and extend all PyTM features, including: - **Custom Threat Patterns**: Define new threat types and detection logic. - **Advanced Dataflow Modeling**: Use PyTM's dataflow and element types. diff --git a/docs/features.md b/docs/features.md index 2d964a8..8f4512b 100644 --- a/docs/features.md +++ b/docs/features.md @@ -78,7 +78,7 @@ See [docs/gdaf.md](gdaf.md) for the complete reference including the context YAM ## GitHub Action -SecOpsTM ships as an **official GitHub Action** (`action.yml`) for threat-model-as-code CI/CD: +SecOpsTM ships as an **official GitHub Action** (`action.yml`) for threat-modeling-as-code CI/CD: ```yaml - uses: your-org/secopstm@v1 @@ -120,7 +120,7 @@ See `.github/workflows/threat-model.yml` for the example workflow. - **Graphical Editor**: Visual drag-and-drop canvas for building models without writing Markdown. - **Reports are fully self-contained** and work offline. -## Threat Model Templates +## System Model Templates Ready-to-use DSL templates in `threatModel_Template/`: diff --git a/docs/gdaf.md b/docs/gdaf.md index 1c88db8..da6e87d 100644 --- a/docs/gdaf.md +++ b/docs/gdaf.md @@ -23,7 +23,7 @@ GDAF reads a YAML file that defines `attack_objectives`, `threat_actors`, and `r ### Step 2 — Build a directed graph -`GDAFEngine._build_graph()` creates a node for every actor and server defined in the threat model. Dataflows become directed edges carrying metadata (protocol, encryption, authentication). In project mode, the graph is unified across the main model and all sub-models. +`GDAFEngine._build_graph()` creates a node for every actor and server defined in the system model. Dataflows become directed edges carrying metadata (protocol, encryption, authentication). In project mode, the graph is unified across the main model and all sub-models. ### Step 3 — Identify entry points diff --git a/docs/getting_started.md b/docs/getting_started.md index ba8ebfb..8fe326c 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -116,5 +116,5 @@ The framework includes a web-based interface for interactive threat modeling, ac 2. **Open your browser** to the address shown in the console (usually `http://127.0.0.1:5000/`). 3. **Choose a mode:** - - **Simple Mode**: Ideal for quick visualization and editing of threat models written in Markdown. It features a live preview and now supports multi-file projects through a tabbed interface, allowing you to edit a main model and its sub-models together. - - **Graphical Editor**: A visual, drag-and-drop canvas for building threat models from scratch without writing Markdown. This mode is under active development. + - **Simple Mode**: For quick visualization and editing of system models written in Markdown. It features a live preview and supports multi-file projects through a tabbed interface, so you can edit a main model and its sub-models together. + - **Graphical Editor**: A visual, drag-and-drop canvas for building system models from scratch without writing Markdown. This mode is under active development. diff --git a/docs/index.md b/docs/index.md index 54e34d5..e5fd3bd 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,12 +1,12 @@ -# Elevating Cyber Resilience with Automated Threat Modeling +# SecOpsTM Documentation -Welcome to the official documentation for **SecOpsTM** — a STRIDE threat modeling framework -with MITRE ATT&CK mapping, AI-enhanced threat generation, and interactive diagram export. +**SecOpsTM** is a STRIDE threat modeling framework with MITRE ATT&CK mapping, AI-enhanced +threat generation, and interactive diagram export. -In an era of escalating cyber threats and rapid development cycles, traditional security practices -often fall short. SecOpsTM bridges that gap by embodying **Threat Modeling as Code (TMasC)**: -threat models are human-readable, version-controlled Markdown files that integrate naturally into -CI/CD pipelines and cross-functional workflows. +It follows a **Threat Modeling as Code (TMasC)** approach: you describe your system in a +human-readable, version-controlled Markdown file (the system model), and SecOpsTM generates +the threat model from it — a workflow that fits naturally into CI/CD pipelines and +cross-functional review. ## Why Automated Threat Modeling? @@ -23,17 +23,17 @@ CI/CD pipelines and cross-functional workflows. - **AI-enhanced generation** — LLM + RAG pipeline surfaces threats beyond rule-based analysis - **Context-aware severity** — scoring adjusts for encryption, authentication, network exposure, CVE signals, and D3FEND mitigations - **Hierarchical modeling** — decompose large systems into linked sub-models with drill-down diagrams -- **Comprehensive exports** — HTML reports, STIX 2.1, ATT&CK Navigator layers, SVG diagrams, ZIP bundles -- **IaC integration** — generate threat models directly from Ansible and Terraform configurations +- **Exports** — HTML reports, STIX 2.1, ATT&CK Navigator layers, SVG diagrams, ZIP bundles +- **IaC integration** — generate system models directly from Ansible and Terraform configurations ## Documentation - [Getting Started](getting_started.md) — Installation, first run, web editor -- [**Claude Code Skill**](claude-code-skill.md) — AI-assisted threat model generation: describe your system, Claude writes the DSL -- [**Workflow: Conception to Run**](workflow.md) — Complete end-to-end guide: conception, modeling, enrichment, CI/CD +- [**Claude Code Skill**](claude-code-skill.md) — AI-assisted system model generation: describe your system, Claude writes the DSL +- [**Workflow: Conception to Run**](workflow.md) — End-to-end guide: conception, modeling, enrichment, CI/CD - [Usage](usage.md) — CLI flags, project mode, export formats -- [Features](features.md) — Full feature list: AI engines, diagrams, exports -- [Defining Threat Models](defining_threat_models.md) — Markdown DSL reference +- [Features](features.md) — Feature list: AI engines, diagrams, exports +- [Defining Your System Model](defining_threat_models.md) — Markdown DSL reference - [Enriching AI Threats](enriching_ai_threats.md) — DSL attributes, BOM, and context files that improve AI-generated threats - [Data Collection Guide](data_collection_guide.md) — What information to gather before threat modeling - [Examples](examples.md) — Ready-to-use model templates diff --git a/docs/project_onboarding_questionnaire.md b/docs/project_onboarding_questionnaire.md index 45a9106..e71308e 100644 --- a/docs/project_onboarding_questionnaire.md +++ b/docs/project_onboarding_questionnaire.md @@ -234,4 +234,4 @@ Before handing this to the security team, confirm: --- -*SecOpsTM — Threat Model as Code. For questions, see `docs/getting_started.md` or open an issue.* +*SecOpsTM — Threat Modeling as Code. For questions, see `docs/getting_started.md` or open an issue.* diff --git a/docs/technical_documentation/architecture_overview.md b/docs/technical_documentation/architecture_overview.md index 0501492..b29409d 100644 --- a/docs/technical_documentation/architecture_overview.md +++ b/docs/technical_documentation/architecture_overview.md @@ -229,7 +229,7 @@ sub-model internals transparently. ## Sub-model Drill-down -Sub-model drill-down connects a server in a parent model to a child threat model file via the +Sub-model drill-down connects a server in a parent model to a child system model file via the `submodel=./path/to/model.md` DSL keyword. The child IS the parent server at higher detail. ### Parent diagram (hyperlink nodes) @@ -288,9 +288,9 @@ The framework uses a pluggable AI provider architecture to support various Large * **Role**: A dedicated provider for local Ollama instances. * **Function**: Uses direct HTTP calls to the Ollama API for low-latency local inference. -## AI-Powered Threat Model Generation and Modification +## AI-Powered System Model Generation and Modification -This section details the architecture behind the AI-driven generation and modification of threat models, available primarily through the "simple mode" interface. +This section details the architecture behind the AI-driven generation and modification of system models, available primarily through the "simple mode" interface. ### Workflow: @@ -306,7 +306,7 @@ This section details the architecture behind the AI-driven generation and modifi 3. **Service Layer Logic (`threat_analysis/server/ai_service.py`)**: * The `AiService` (formerly integrated into the threat model service) is the central orchestrator. * It utilizes `LiteLLMClient` to communicate with the configured AI provider. - * It supports streaming responses, allowing the user to see the threat model being generated in real-time. + * It supports streaming responses, allowing the user to see the system model being generated in real-time. 4. **AI Provider Interaction**: * Based on `config/ai_config.yaml`, the `LiteLLMClient` identifies the enabled provider (e.g., `gemini`, `openai`, or `ollama`). @@ -316,7 +316,7 @@ This section details the architecture behind the AI-driven generation and modifi * The generated Markdown content is streamed back to the frontend. * The CodeMirror editor is updated, and the diagram is re-rendered. -This architecture enables an iterative threat modeling process, allowing users to start with a basic model and refine it incrementally through natural language commands, significantly enhancing the usability and flexibility of the tool compared to an overwrite-only approach. +This architecture enables an iterative threat modeling process, allowing users to start with a basic model and refine it incrementally through natural language commands, improving the usability and flexibility of the tool compared to an overwrite-only approach. ## Frontend JavaScript Architecture (Graphical Editor) @@ -355,13 +355,13 @@ The JavaScript codebase for the graphical editor (`threat_analysis/server/templa * **Details**: Utilizes `NodeManager` to create new nodes and `PropertiesPanelManager` to immediately display their properties upon creation. * **`ThreatModelGenerator.js`**: - * **Role**: Handles the process of converting the visual graph into a structured threat model representation and initiating the backend generation process. - * **Function**: Collects all nodes and connections from the canvas, constructs a JSON representation of the threat model, converts it to Markdown, and sends it to the `/api/generate_all` endpoint. + * **Role**: Handles the process of converting the visual graph into a structured system model representation and initiating the backend generation process. + * **Function**: Collects all nodes and connections from the canvas, constructs a JSON representation of the system model, converts it to Markdown, and sends it to the `/api/generate_all` endpoint. * **Details**: Also responsible for displaying generation status and results. * **`ModelManager.js`**: - * **Role**: Manages loading and saving threat models from the server or local files. + * **Role**: Manages loading and saving system models from the server or local files. * **Function**: Handles interactions with the "Open Model" modal, fetches lists of saved models, loads models via API calls, and handles local file uploads (Markdown and metadata JSON). * **Details**: Utilizes `NodeManager` and `ConnectionManager` to repopulate the graph from loaded model data and positions. -This modular design promotes reusability, testability, and a clear separation of concerns, making the graphical editor more robust and easier to extend. \ No newline at end of file +This modular design promotes reusability, testability, and a clear separation of concerns, making the graphical editor easier to maintain and extend. \ No newline at end of file diff --git a/docs/technical_documentation/index.md b/docs/technical_documentation/index.md index 2f1d2fd..2781223 100644 --- a/docs/technical_documentation/index.md +++ b/docs/technical_documentation/index.md @@ -4,17 +4,17 @@ ### 1.1. The Challenge: From Manual Diagrams to Automated Analysis -As software systems grow in complexity, proactively identifying security vulnerabilities during the design phase is significantly more effective than reacting to them post-deployment. Threat modeling provides a structured process for this, but traditional approaches often rely on manual diagramming and static documents that are difficult to maintain and impossible to integrate into automated development pipelines. +As software systems grow in complexity, proactively identifying security vulnerabilities during the design phase is more effective than reacting to them post-deployment. Threat modeling provides a structured process for this, but traditional approaches often rely on manual diagramming and static documents that are difficult to maintain and impossible to integrate into automated development pipelines. -This document provides a detailed technical overview of the **SecOpsTM** framework, a tool designed to address these challenges by treating the threat model as a living artifact that evolves with the system itself. +This document provides a detailed technical overview of the **SecOpsTM** framework, a tool designed to address these challenges by treating the system model as a living artifact that evolves with the system itself. -### 1.2. Core Philosophy: System-Level Threat Model as Code +### 1.2. Core Philosophy: System-Level Threat Modeling as Code -The guiding philosophy of this framework is **Threat Model as Code (TMaC)**, applied at the **system level**. Instead of focusing on abstract application components, our approach defines the entire system architecture—including infrastructure, network boundaries, and data flows—in a simple, version-controllable format. +The guiding philosophy of this framework is **Threat Modeling as Code (TMasC)**, applied at the **system level**. Instead of focusing on abstract application components, our approach defines the entire system architecture—including infrastructure, network boundaries, and data flows—in a simple, version-controllable format. -This is particularly powerful when generating models directly from **Infrastructure as Code (IaC)** sources like Ansible playbooks. By parsing the same files that define the deployed environment, the framework creates a threat model that is a true representation of the running system. This enables a seamless, automated workflow where changes in infrastructure are immediately reflected in the threat analysis. +This is especially useful when generating models directly from **Infrastructure as Code (IaC)** sources like Ansible playbooks. By parsing the same files that define the deployed environment, the framework creates a system model that is a true representation of the running system. This enables an automated workflow where changes in infrastructure are immediately reflected in the threat analysis. -By defining the system in a Markdown DSL, the threat model becomes: +By defining the system in a Markdown DSL, the system model becomes: - **Versioned**: Stored in Git to track its evolution alongside the source code and infrastructure code. - **Automated**: Integrated directly into CI/CD pipelines to run analysis on every change. - **Collaborative**: Developers and operations engineers can contribute using the same tools and workflows they use for code. @@ -25,17 +25,17 @@ To understand the unique value of SecOpsTM, it's useful to compare it to other p | Feature | Microsoft TMT | OWASP Threat Dragon | SecOpsTM (This Tool) | | :--- | :--- | :--- | :--- | -| **Primary Paradigm** | GUI-based Diagramming | Web-based Diagramming | **Threat Model as Code (TMaC)** | +| **Primary Paradigm** | GUI-based Diagramming | Web-based Diagramming | **Threat Modeling as Code (TMasC)** | | **Input Format** | Proprietary `.tm7` format | JSON, with a web UI | **Markdown (DSL) / IaC Playbooks** | | **Automation & CI/CD** | None. Fully manual process. | Limited. Has an API but is not designed for pipeline integration. | **Core Feature**. Designed to be run from the CLI in a pipeline. | | **IaC Integration** | None. | None. | **Yes (Ansible)**. Can generate a model directly from infrastructure definitions. | -| **Version Control** | Possible by archiving the `.tm7` model file. However, the format is complex (XML-based) and not well-suited for line-by-line diffing or merging. | Feasible (JSON), but the diagram is the primary source of truth, not the code. | **Seamless**. Markdown is text-based and ideal for Git. | +| **Version Control** | Possible by archiving the `.tm7` model file. However, the format is complex (XML-based) and not well-suited for line-by-line diffing or merging. | Feasible (JSON), but the diagram is the primary source of truth, not the code. | **Direct**. Markdown is text-based and ideal for Git. | | **Extensibility** | Limited to templates. | Good. Open-source and extensible. | **High**. Mappings and logic are in simple Python dictionaries and modules. | | **Visualization & Reporting** | Basic reports. | Printable report, basic threat view. | Rich HTML reports, STIX 2.1 export, **MITRE ATT&CK Navigator layers**. | ## 3. High-Level Architecture -The framework is a Python-based application that can be run as a command-line tool (for automation) or a web server (for interactive editing). It ingests a threat model source and produces a suite of artifacts. +The framework is a Python-based application that can be run as a command-line tool (for automation) or a web server (for interactive editing). It ingests a system model source and produces a suite of artifacts. ```mermaid graph TD @@ -76,7 +76,7 @@ graph TD ## 4. Technical Deep Dive: Module by Module -This section provides a comprehensive breakdown of each component of the SecOpsTM framework. +This section provides a detailed breakdown of each component of the SecOpsTM framework. ### 4.1. Entrypoint and Orchestration (`threat_analysis/__main__.py`) @@ -93,7 +93,7 @@ The execution of the framework begins in `__main__.py`. This script is responsib The `ThreatModel` class is the heart of the framework, serving as the in-memory representation of the system under analysis. It is designed to be a rich, stateful object that not only holds the architectural components but also orchestrates the analysis process. -- **A Wrapper Around PyTM**: At its core, the `ThreatModel` class wraps a `pytm.TM` object. This allows the framework to leverage the foundational threat generation logic of the PyTM library while extending it with custom features, more detailed component attributes, and advanced analysis capabilities. +- **A Wrapper Around PyTM**: At its core, the `ThreatModel` class wraps a `pytm.TM` object. This allows the framework to use the foundational threat generation logic of the PyTM library while extending it with custom features, more detailed component attributes, and advanced analysis capabilities. - **Self-Initialization of MitreMapping**: The `ThreatModel` class now self-initializes its `MitreMapping` instance. This simplifies the constructor of `ThreatModel` and centralizes the management of the `MitreMapping` dependency within the `ThreatModel` itself, improving modularity and testability. @@ -118,7 +118,7 @@ The `ThreatModel` class is the heart of the framework, serving as the in-memory 5. **Grouping**: It groups all generated threats (both from PyTM and custom rules) by their STRIDE category. 6. **MITRE Analysis**: Finally, it triggers the MITRE ATT&CK mapping and enrichment process. -- **`get_all_threats_details()`**: This method provides a clean, comprehensive list of all identified threats, including their description, target, STRIDE category, severity, and associated MITRE techniques. This is the primary data source for the report generation modules. +- **`get_all_threats_details()`**: This method provides a clean, complete list of all identified threats, including their description, target, STRIDE category, severity, and associated MITRE techniques. This is the primary data source for the report generation modules. ### 4.3. Model Parsing and Validation @@ -141,19 +141,19 @@ The framework's custom threat generation is driven by a flexible, rule-based eng The core of the framework leverages the `pytm` library for its foundational threat generation capabilities. When `threat_model.process_threats()` is called, `pytm` automatically analyzes the defined architecture (actors, servers, dataflows, and their properties) to identify potential STRIDE threats. **How PyTM Generates Threats:** -`pytm` applies a set of predefined rules based on the relationships and properties of elements in the threat model. For example: +`pytm` applies a set of predefined rules based on the relationships and properties of elements in the system model. For example: * A dataflow between an actor and a server might trigger "Spoofing" or "Repudiation" threats. * Dataflows marked as unencrypted (`is_encrypted=False`) can lead to "Information Disclosure" threats. * Servers with specific stereotypes (e.g., "Database") might generate threats related to data tampering or unauthorized access. **Influencing PyTM's Threats:** -To "add" or "remove" threats generated directly by `pytm`, you primarily need to modify the underlying architecture of your threat model. This includes: +To "add" or "remove" threats generated directly by `pytm`, you primarily need to modify the underlying architecture of your system model. This includes: * **Adding/Removing Elements:** Introducing new actors, servers, or dataflows can trigger new `pytm` threats. Conversely, removing elements can eliminate threats associated with them. * **Modifying Element Properties:** Changing properties like `is_encrypted` for dataflows, or `stereotype` for servers, can alter the set of threats `pytm` generates. * **Structuring Boundaries:** How elements are placed within trust boundaries can also influence `pytm`'s threat identification. **Filtering PyTM Threats:** -While `pytm` generates a comprehensive set of threats, the framework allows for post-processing and filtering. The `_expand_class_targets` method in `models_module.py` and the overall threat processing pipeline can be extended to filter or modify `pytm`-generated threats before they are presented in reports. +While `pytm` generates a broad set of threats, the framework allows for post-processing and filtering. The `_expand_class_targets` method in `models_module.py` and the overall threat processing pipeline can be extended to filter or modify `pytm`-generated threats before they are presented in reports. ### 4.4.2. Customizing PyTM's Threat Database @@ -232,7 +232,7 @@ The framework does not currently expose a direct `-ignore` parameter for `pytm`- ``` This rule states: "If a dataflow is not encrypted AND uses the HTTP protocol, then generate an 'Information Disclosure' threat." -- **`custom_threats.py`: The Engine Itself**: This module contains the `get_custom_threats` function which acts as the engine that interprets the rules. This module has been refactored to use a more robust property lookup mechanism (`_get_property`) that supports nested attributes (e.g., `source.inBoundary.isTrusted`), making the rule application more powerful and flexible. +- **`custom_threats.py`: The Engine Itself**: This module contains the `get_custom_threats` function which acts as the engine that interprets the rules. This module has been refactored to use a more resilient property lookup mechanism (`_get_property`) that supports nested attributes (e.g., `source.inBoundary.isTrusted`), making the rule application more flexible. - **`get_custom_threats(threat_model)`**: This function is the main entry point for the custom threat generation process. It takes the fully parsed `ThreatModel` object as input. - **Iteration and Matching**: The function iterates through every component (server, dataflow, actor, etc.) in the `threat_model`. For each component, it retrieves the relevant rules from `THREAT_RULES` (based on the component's type). It then checks if the component's properties match all the `conditions` specified in a rule. - **Boundary-Aware Logic**: A key feature of the engine is its ability to handle complex conditions, especially for dataflows. It can check the properties of the source and sink of a dataflow, including which boundary they are in. For example, a rule can be written to only trigger a threat if a dataflow crosses from an untrusted boundary (like the "Internet") to a trusted one (like the "Internal Network"). @@ -264,7 +264,7 @@ graph TD ### 4.5. The STRIDE, CAPEC, and ATT&CK Mapping (`mitre_mapping_module.py`) -This is the most complex and critical module for enriching the raw threat data. It transforms high-level STRIDE threats into specific, actionable MITRE ATT&CK techniques through a chained mapping process that leverages established cybersecurity knowledge bases. The module was recently updated to fix a bug where the D3FEND mitigation name was not being correctly displayed in the report. The fix involved updating the regex to correctly parse the mitigation name from the `ATTACK_D3FEND_MAPPING` and updating the report template to display the name instead of the description. +This is the most complex and critical module for enriching the raw threat data. It transforms high-level STRIDE threats into specific, actionable MITRE ATT&CK techniques through a chained mapping process that uses established cybersecurity knowledge bases. The module was recently updated to fix a bug where the D3FEND mitigation name was not being correctly displayed in the report. The fix involved updating the regex to correctly parse the mitigation name from the `ATTACK_D3FEND_MAPPING` and updating the report template to display the name instead of the description. - **`MitreMapping` Class**: The central class that orchestrates the entire enrichment pipeline. - **Initialization**: When instantiated, it pre-loads and processes several external data sources to build its mapping tables: @@ -308,7 +308,7 @@ The `SeverityCalculator` provides a nuanced risk score for each threat. - **Multi-Factor Calculation**: The final score is not a static value but a composite calculated from: 1. **Base Score**: A default score for each STRIDE category. 2. **Rule-Defined Score**: The impact and likelihood values (1-5) defined in the `threat_rules.py` entry for that threat. - 3. **Target Multipliers**: The score can be increased by multipliers defined in the `## Severity Multipliers` section of the threat model, which are loaded from the markdown file. + 3. **Target Multipliers**: The score can be increased by multipliers defined in the `## Severity Multipliers` section of the system model, which are loaded from the markdown file. 4. **Protocol Adjustments**: The protocol of a dataflow can adjust the score (e.g., HTTP increases it, HTTPS decreases it). 5. **Data Classification**: The classification of the data in a flow (`PUBLIC`, `SECRET`, etc.) acts as a final multiplier. - **Normalization**: The final score is clamped between 1.0 and 10.0 and assigned a qualitative level (e.g., "HIGH", "CRITICAL"). @@ -356,7 +356,7 @@ Results appear in a **"⛓️ Attack Chain Analysis"** section in the HTML repor - It uses a Jinja2 template (`threat_model.dot.j2`) to generate Graphviz DOT language code from the `ThreatModel` object. - **Trust Boundary Colors**: Trusted boundaries use `color="#2e7d32"` (dark green, solid); untrusted use `color="#c62828"` (dark red, dashed). These values are baked into the DOT template and thus appear in both exported SVG and HTML diagrams. - **Severity Heat Map**: `_generate_html_with_legend()` accepts `severity_map` (dict of component → severity label) and `report_url`. The HTML diagram template injects these as JavaScript variables, enabling a toggle button that applies/restores per-component colour overlays and shows hover tooltips with "View threats →" deep-links into the HTML report. - - **Visual Styling**: The generator includes sophisticated logic for rich visual styling, combining native Graphviz shapes with embedded SVG icons. The layout of the icon and text is adjusted based on the element type for maximum clarity: + - **Visual Styling**: The generator includes detailed logic for rich visual styling, combining native Graphviz shapes with embedded SVG icons. The layout of the icon and text is adjusted based on the element type for maximum clarity: - **Native Shapes & Sizing**: It assigns semantic shapes to elements and sets their sizes for a clean visual hierarchy: - **Actors**: Rendered as fixed-size **circles**. - **Switches and Firewalls**: Rendered as fixed-size diamonds and hexagons, respectively, which are smaller than other nodes for visual distinction. @@ -399,7 +399,7 @@ graph TD ``` - **`stix_generator.py`**: This module provides interoperability. - It translates the framework's findings into STIX 2.1, a standardized language for cyber threat intelligence. - - It leverages the `attack-flow` STIX extension to create a structured representation of the attack chains, creating `attack-action` and `attack-asset` objects and linking them with relationships. + - It uses the `attack-flow` STIX extension to create a structured representation of the attack chains, creating `attack-action` and `attack-asset` objects and linking them with relationships. - **`attack_navigator_generator.py`**: This module creates a JSON layer file compatible with the [MITRE ATT&CK Navigator](https://mitre-attack.github.io/attack-navigator/) to visualize the results of the analysis. - The `AttackNavigatorGenerator` class takes the threat model's name and a list of all detailed threats. - It processes the threats to extract all unique ATT&CK techniques. For each technique, it aggregates the findings, using the highest severity score as the technique's score and compiling the descriptions of all threats mapped to it in the comments. @@ -409,7 +409,7 @@ graph TD - **`server.py`**: A simple Flask application that defines the API endpoints: - `/`: Serves the main `web_interface.html`. - - `/fullGUI`: Serves the `full_gui.html` with a more comprehensive interface. + - `/fullGUI`: Serves the `full_gui.html` with a fuller interface. - `/api/update`: Receives Markdown from the editor, triggers a live analysis, and returns the resulting SVG diagram and legend. - `/api/export` & `/api/export_all`: Handle requests to download the generated artifacts. - **`threat_model_service.py`**: This service layer acts as a bridge between the web server and the core analysis engine. It encapsulates the logic for handling web requests, calling the appropriate framework components, and managing temporary files, keeping the Flask app clean and focused on routing. It has been updated to align with the new `create_threat_model` signature, removing the `mitre_mapping` argument from its calls. @@ -426,8 +426,8 @@ This module provides actionable mitigation advice for the threats identified dur - **`get_framework_mitigation_suggestions()` Function**: This is the primary function exposed by the module. - It takes a list of ATT&CK technique IDs (extracted from the threats during the report generation phase). - - It now leverages the `MITRE_TO_CIS_MAP` (generated by `_create_mitre_to_cis_map`) to provide CIS control suggestions. - - The `FRAMEWORK_MITIGATION_MAP` has been updated with more relevant OWASP ASVS entries, particularly for Information Disclosure threats, ensuring more comprehensive and accurate mitigation advice. + - It now uses the `MITRE_TO_CIS_MAP` (generated by `_create_mitre_to_cis_map`) to provide CIS control suggestions. + - The `FRAMEWORK_MITIGATION_MAP` has been updated with more relevant OWASP ASVS entries, particularly for Information Disclosure threats, giving more complete and accurate mitigation advice. - **Architecture Flow**: ```mermaid @@ -456,7 +456,7 @@ Key configurations include: ### 4.11. IaC Plugin Architecture (`iac_plugins/`) -The framework is designed to be extensible through a dedicated Infrastructure as Code (IaC) plugin system, allowing it to generate threat models from various IaC sources. +The framework is designed to be extensible through a dedicated Infrastructure as Code (IaC) plugin system, allowing it to generate system models from various IaC sources. - **Abstract Base Class**: The `iac_plugins/__init__.py` file defines an abstract base class called `IaCPlugin`. To create a new plugin, a developer must create a class that inherits from `IaCPlugin`. - **Required Implementations**: Any new plugin must implement three key methods: @@ -467,14 +467,14 @@ The framework is designed to be extensible through a dedicated Infrastructure as ### 4.12. Ansible Plugin and Metadata (`iac_plugins/ansible_plugin.py`) -The Ansible plugin is a concrete implementation of the IaC plugin architecture, designed to translate an existing Ansible project into a threat model. It works by combining information from the Ansible playbook and inventory with a dedicated metadata structure that describes the security-relevant aspects of the architecture. +The Ansible plugin is a concrete implementation of the IaC plugin architecture, designed to translate an existing Ansible project into a system model. It works by combining information from the Ansible playbook and inventory with a dedicated metadata structure that describes the security-relevant aspects of the architecture. - **How it Works**: The plugin is triggered when the `--ansible-path` argument is used, pointing to a main playbook file (e.g., `playbook.yml`). 1. **Parsing**: The plugin first parses the specified playbook. It also looks for a corresponding inventory file named `hosts.ini` in the same directory. - 2. **Metadata Extraction**: The crucial step is the extraction of a special variable named `threat_model_metadata` from the `vars` section of the playbook. This variable must be a dictionary that contains the threat model definition. - 3. **Model Generation**: The plugin then uses the data from the `threat_model_metadata` dictionary to generate the components of the threat model (Boundaries, Actors, Servers, Dataflows) in the Markdown DSL format. + 2. **Metadata Extraction**: The crucial step is the extraction of a special variable named `threat_model_metadata` from the `vars` section of the playbook. This variable must be a dictionary that contains the system model definition. + 3. **Model Generation**: The plugin then uses the data from the `threat_model_metadata` dictionary to generate the components of the system model (Boundaries, Actors, Servers, Dataflows) in the Markdown DSL format. -- **The `threat_model_metadata` Structure**: This is the core concept for the Ansible integration. Instead of trying to infer the entire architecture from Ansible tasks and roles (which can be ambiguous), the framework requires the user to explicitly define the threat model's structure within the playbook itself. This approach keeps the threat model definition alongside the infrastructure code that it describes. +- **The `threat_model_metadata` Structure**: This is the core concept for the Ansible integration. Instead of trying to infer the entire architecture from Ansible tasks and roles (which can be ambiguous), the framework requires the user to explicitly define the system model's structure within the playbook itself. This approach keeps the system model definition alongside the infrastructure code that it describes. - The `threat_analysis/iac_plugins/ansible_threat_model_config.yml` file serves as a **template or example** of what this `threat_model_metadata` variable should look like. It is **not** a configuration file that is read by the plugin. - The user is expected to copy and adapt this structure into the `vars` section of their own Ansible playbook. @@ -502,7 +502,7 @@ The Ansible plugin is a concrete implementation of the IaC plugin architecture, roles: - webserver ``` - In this example, the `threat_model_metadata` variable is defined directly within the playbook. The plugin will parse this variable to create the "Public DMZ" and "Internal Network" boundaries, the "WebApp Server" component, and the "User Traffic" dataflow. The use of Ansible variables like `{{ ansible_default_ipv4.address }}` within the metadata is also supported, allowing the threat model to be dynamically updated with information from the inventory. + In this example, the `threat_model_metadata` variable is defined directly within the playbook. The plugin will parse this variable to create the "Public DMZ" and "Internal Network" boundaries, the "WebApp Server" component, and the "User Traffic" dataflow. The use of Ansible variables like `{{ ansible_default_ipv4.address }}` within the metadata is also supported, allowing the system model to be dynamically updated with information from the inventory. ## 5. Tooling and Data Maintenance @@ -534,7 +534,7 @@ The framework relies on external data from MITRE ATT&CK, CAPEC, and other source ### 4.13. Attack Flow Generation (`generation/attack_flow_generator.py`) -This module is responsible for generating visual, end-to-end attack scenarios based on the identified threats, compatible with the [Attack Flow](https://attackflow.io/) tool. It has been significantly enhanced to provide more realistic and optimized attack paths. +This module is responsible for generating visual, end-to-end attack scenarios based on the identified threats, compatible with the [Attack Flow](https://attackflow.io/) tool. It has been enhanced to provide more realistic and optimized attack paths. - **Core Philosophy**: The generator's goal is to transform a flat list of threats into meaningful narratives. It achieves this by sequencing threats according to the logical progression of adversary tactics as defined by the MITRE ATT&CK framework. - **Path Discovery Logic (`_find_attack_paths`)**: @@ -550,7 +550,7 @@ This module is responsible for generating visual, end-to-end attack scenarios ba - It filters out threats targeting generic classes (e.g., `pytm.Server` class itself) or tuples of classes, focusing only on threats against specific asset instances. - **Diagram Structure and Content**: - Each generated `.afb` file represents a single, complete attack path. - - The diagram illustrates the progression by alternating between **Actions** (MITRE ATT&CK techniques) and **Assets** (the specific components from your threat model). + - The diagram illustrates the progression by alternating between **Actions** (MITRE ATT&CK techniques) and **Assets** (the specific components from your system model). - The final node in the chain is a conceptual `asset` representing the adversary's objective, derived from the STRIDE category of the final threat (e.g., "Impact: Tampering"). - The resulting flow visually communicates a clear narrative: `(Action 1) -> (targets Asset A) -> (enabling Action 2) -> (targets Asset B) -> ... -> (achieves Impact)`. - **Asset Name Resolution**: Uses dedicated helper methods (`_get_target_name`, `_extract_name_from_object`) to accurately resolve asset names from raw `pytm` objects, ensuring human-readable labels in the generated attack flows. diff --git a/docs/threat_modeling_guide.md b/docs/threat_modeling_guide.md index c55bcfa..90c85aa 100644 --- a/docs/threat_modeling_guide.md +++ b/docs/threat_modeling_guide.md @@ -1,6 +1,6 @@ -# How to Create a Threat Model with SecOpsTM DSL +# How to Create a System Model with SecOpsTM DSL -This guide walks you through building a complete, production-quality threat model for a real +This guide walks you through building a complete, production-quality system model for a real system. By the end, you will have a working SecOpsTM model file (`.md`) that produces STRIDE threat reports, MITRE ATT&CK mappings, GDAF attack paths, and navigable HTML diagrams. @@ -68,7 +68,7 @@ Multi-line definitions are supported — indent continuation lines: Start the file with: ```markdown -# Threat Model: My Application Name +# System Model: My Application Name ## Description Brief description of the system under analysis. Include: diff --git a/docs/usage.md b/docs/usage.md index 1acfb3f..8438457 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -2,13 +2,13 @@ This framework supports two modes of operation: Command Line Interface (CLI) for automated analysis and a Web-based Graphical User Interface (GUI) for interactive editing and visualization. -## Threat Model as Code Philosophy +## Threat Modeling as Code Philosophy -This framework is designed to be used in a "Threat Model as Code" workflow. This means that the threat model is defined in a simple, version-controllable format (Markdown), and the analysis is performed by running a script. This approach has several advantages: +This framework is designed to be used in a "Threat Modeling as Code" workflow. This means that the system model is defined in a simple, version-controllable format (Markdown), and the threat analysis is performed by running a script. This approach has several advantages: -- **Version Control**: Threat models can be stored in a Git repository, allowing you to track changes over time. +- **Version Control**: System models can be stored in a Git repository, allowing you to track changes over time. - **Automation**: The threat modeling process can be integrated into your CI/CD pipeline, allowing you to automatically update your threat model whenever your architecture changes. -- **Collaboration**: Developers can collaborate on the threat model using the same tools they use for code. +- **Collaboration**: Developers can collaborate on the system model using the same tools they use for code. ## 0. Data layers and installation @@ -87,7 +87,7 @@ The named volume `secopstm-rag` persists across container restarts and image reb | Output reports | `-v $(pwd)/output:/app/output` | Files land in `output//` on the host | | AI config | `-v $(pwd)/ai_config.yaml:/app/config/ai_config.yaml:ro` | Switch provider/model without rebuilding | | Prompts | `-v $(pwd)/prompts.yaml:/app/config/prompts.yaml:ro` | Override LLM prompts | -| Threat model files | `-v $(pwd)/models:/models` | Pass `--model-file /models/model.md` | +| System model files | `-v $(pwd)/models:/models` | Pass `--model-file /models/model.md` | | CVE definitions | `-v $(pwd)/cve_definitions.yml:/app/cve_definitions.yml:ro` | Per-asset CVE list | | RAG vector store | `-v secopstm-rag:/app/rag` | Named volume, required for RAG | @@ -174,7 +174,7 @@ secopstm --server You can also still use `python -m threat_analysis` with all the same flags — they are 100% equivalent. -1. **Learn how to define your threat model in Markdown** by reading the [Defining Your Threat Model](defining_threat_models.md) guide. +1. **Learn how to define your system model in Markdown** by reading the [Defining Your System Model](defining_threat_models.md) guide. 2. **Generate Attack Flow diagrams:** Add the `--attack-flow` flag to generate `.afb` files for key STRIDE objectives (Tampering, Spoofing, Information Disclosure, Repudiation). ```bash secopstm --model-file path/to/your_model.md --attack-flow @@ -200,7 +200,7 @@ Example: python -m threat_analysis --model-file path/to/your_model.md --implemented-mitigations-file path/to/your/mitigations.txt --cve-definitions-file path/to/your/cves.yml ``` -Here's a more comprehensive example using test files: +Here's another example using test files: ```bash python -m threat_analysis --model-file threatModel_Template/threat_model.md \ --navigator \ @@ -209,9 +209,9 @@ python -m threat_analysis --model-file threatModel_Template/threat_model.md \ --cve-definitions-file tests/cve_definitions.yml ``` -### 2. Project Mode: Hierarchical Threat Models +### 2. Project Mode: Hierarchical System Models -The framework excels at handling complex projects with multiple, nested threat models. While you can run project-based analysis from the CLI, the recommended workflow is to use the **Web-based User Interface (Server Mode)**, which provides a more interactive and intuitive experience. +The framework handles projects with multiple, nested system models. You can run project-based analysis from the CLI, but the recommended workflow is the **Web-based User Interface (Server Mode)**, which is more interactive. 1. **Organize your project** in a directory, with a `main.md` at the root and sub-models in sub-directories (e.g., `my_project/main.md`, `my_project/backend/model.md`). 2. **Launch the server with your project path:** @@ -222,7 +222,7 @@ The framework excels at handling complex projects with multiple, nested threat m ### 3. Infrastructure as Code (IaC) Integration (Ansible Example) -This framework can automatically generate a complete threat model directly from IaC configurations. It automatically includes a set of default protocol styles from `threatModel_Template/base_protocol_styles.md` to ensure consistent visualization. +This framework can generate a complete system model directly from IaC configurations. It automatically includes a set of default protocol styles from `threatModel_Template/base_protocol_styles.md` to ensure consistent visualization. Here's how to use the Ansible plugin with a sample playbook: @@ -231,7 +231,7 @@ Here's how to use the Ansible plugin with a sample playbook: ```bash python -m threat_analysis --ansible-path tests/ansible_playbooks/simple_web_server/simple_web_server.yml ``` - This command will generate a complete threat model based on the Ansible playbook. The generated Markdown model will be saved in the `output/` directory with a filename derived from your Ansible playbook (e.g., `simple_web_server.md`). + This command will generate a complete system model based on the Ansible playbook. The generated Markdown model will be saved in the `output/` directory with a filename derived from your Ansible playbook (e.g., `simple_web_server.md`). If you wish to specify a different output file for the generated model, you can use the `--model-file` option: ```bash @@ -241,7 +241,7 @@ Here's how to use the Ansible plugin with a sample playbook: ### 4. CVE-Based Threat Generation (Optional) -This framework can generate threats based on a list of Common Vulnerabilities and Exposures (CVEs) that you provide for specific components in your threat model. +This framework can generate threats based on a list of Common Vulnerabilities and Exposures (CVEs) that you provide for specific components in your system model. #### CVE data source @@ -260,7 +260,7 @@ The tool maps your specified CVEs to CAPEC attack patterns, which are then used 1. **Create `cve_definitions.yml`**: By default, the tool looks for `cve_definitions.yml` in the directory of the model or project. You can override this path using the `--cve-definitions-file` command-line argument. -2. **Define CVEs for your equipment**: In this file, list the equipment (servers or actors from your threat model) and the CVEs associated with them. +2. **Define CVEs for your equipment**: In this file, list the equipment (servers or actors from your system model) and the CVEs associated with them. **Example `cve_definitions.yml`:** ```yaml @@ -301,8 +301,8 @@ For a more interactive experience, the framework provides a web-based UI that ru The console will display the address (e.g., `http://127.0.0.1:5000`) to open in your web browser. 2. **Choose a Mode from the Menu:** - - **Simple Mode**: An interface designed for editing and visualizing threat models described in Markdown. It features a tabbed editor, a live interactive diagram, and full reporting capabilities. When a project is loaded, all model files are automatically opened in separate tabs. - - **Graphical Editor**: A full-featured, interactive canvas to build, modify, and analyze threat models from scratch directly in the browser. It includes a toolbar for adding elements, a properties panel for editing, and the ability to generate all artifacts without touching Markdown directly. + - **Simple Mode**: An interface for editing and visualizing system models described in Markdown. It features a tabbed editor, a live interactive diagram, and full reporting. When a project is loaded, all model files open automatically in separate tabs. + - **Graphical Editor**: An interactive canvas to build, modify, and analyze system models from scratch directly in the browser. It includes a toolbar for adding elements, a properties panel for editing, and the ability to generate all artifacts without touching Markdown directly. ### Working with Projects and Sub-models (Simple Mode) @@ -316,11 +316,11 @@ The Simple Mode is optimized for working with complex, multi-file projects. - **Backend Services**: submodel=backend/model.md, boundary="Internal" ``` -3. **Generate the Full Project**: Click the **"Generate All"** button. The system is designed to be robust: +3. **Generate the Full Project**: Click the **"Generate All"** button: - It gathers the content from all open tabs. - - It intelligently detects if any model references a sub-model that is not currently open. - - If a missing sub-model is found, it will prompt you to select your project's root directory. It then scans this directory to find the missing files and includes them in the generation process. - - This ensures that a complete, unified, and navigable set of reports and diagrams is always generated. + - It detects if any model references a sub-model that is not currently open. + - If a missing sub-model is found, it prompts you to select your project's root directory, then scans this directory to find the missing files and includes them in the generation process. + - The result is a complete, unified, navigable set of reports and diagrams. #### Loading a Project with the Directory Picker @@ -372,7 +372,7 @@ Both the web and CLI interfaces expect files generated by `--output-format json` ## 4. GitHub Action (CI/CD) -SecOpsTM ships as an official GitHub Action for threat-model-as-code workflows: +SecOpsTM ships as an official GitHub Action for threat-modeling-as-code workflows: ```yaml # .github/workflows/threat-model.yml diff --git a/docs/workflow.md b/docs/workflow.md index 82f1eb0..0712a93 100644 --- a/docs/workflow.md +++ b/docs/workflow.md @@ -18,7 +18,7 @@ whiteboard conversation with the system owner to automated reports in CI/CD. gathering) ``` -Phases 1 and 2 are mandatory. Phase 3 is optional but significantly improves AI-generated threats. +Phases 1 and 2 are mandatory. Phase 3 is optional but improves AI-generated threats. Phase 4 can be automated. --- @@ -442,7 +442,7 @@ secopstm --server ## Related Documentation -- [Defining Threat Models](defining_threat_models.md) — complete DSL reference (all attributes, all sections) +- [Defining Your System Model](defining_threat_models.md) — complete DSL reference (all attributes, all sections) - [Enriching AI Threats](enriching_ai_threats.md) — which attributes improve AI output and how - [Data Collection Guide](data_collection_guide.md) — questionnaire for Phase 1 information gathering - [Examples](examples.md) — ready-to-use model templates diff --git a/tests/test_debate_engine.py b/tests/test_debate_engine.py new file mode 100644 index 0000000..6cc6000 --- /dev/null +++ b/tests/test_debate_engine.py @@ -0,0 +1,310 @@ +# Copyright 2025 ellipse2v +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for threat_analysis/core/debate_engine.py""" + +import asyncio +from unittest.mock import MagicMock, AsyncMock + +from threat_analysis.core.debate_engine import RedBlueDebateEngine +from threat_analysis.core.gdaf_engine import AttackScenario, AttackHop +from threat_analysis.core.asset_technique_mapper import ScoredTechnique + + +def make_scenario(path_score=4.5, detection_coverage=0.2, risk_level="CRITICAL"): + hop = AttackHop( + asset_name="WebServer", + asset_type="web_server", + techniques=[ScoredTechnique( + id="T1190", name="Exploit Public-Facing Application", + tactics=["initial-access"], score=1.5, rationale="", + )], + dataflow_name="UserToWeb", + protocol="https", + is_encrypted=True, + is_authenticated=False, + hop_score=1.8, + hop_position="entry", + ) + return AttackScenario( + scenario_id="GDAF-TEST01", + objective_id="obj1", + objective_name="Exfiltrate customer data", + objective_description="", + objective_business_impact="High", + objective_mitre_final_tactic="exfiltration", + actor_id="actor1", + actor_name="Organized Crime", + actor_sophistication="high", + entry_point="ExternalUser", + target_asset="WebServer", + hops=[hop], + path_score=path_score, + risk_level=risk_level, + detection_coverage=detection_coverage, + unacceptable_risk=True, + ) + + +class FakeProvider: + """Returns queued canned responses in call order.""" + def __init__(self, responses): + self._responses = list(responses) + self.calls = [] + + async def generate_debate_turn(self, prompt, system_prompt): + self.calls.append((prompt, system_prompt)) + if not self._responses: + return {} + return self._responses.pop(0) + + +RED_TURN_HIGH = { + "viability_score": 0.8, + "techniques_attempted": ["T1190"], + "failed_alternatives": [], + "rationale": "Exploiting unauthenticated HTTPS endpoint", + "evidence": [ + {"claim": "endpoint has no auth", "evidence_type": "misconfig", + "evidence_ref": "hop 1 is_authenticated=false", "confidence": "high"}, + ], +} +BLUE_TURN_BLOCK_ALL = { + "viability_score": 0.1, + "techniques_blocked": ["T1190"], + "detection_gaps": [ + {"step": "WebServer entry", "control_family": "EDR", "covered": True, + "detail": "EDR rule X blocks T1190 payloads", "confidence": "high"}, + ], + "rationale": "Blocked by EDR rule", + "evidence": [], +} +BLUE_TURN_GAP = { + "viability_score": 0.6, + "techniques_blocked": [], + "detection_gaps": [ + {"step": "WebServer entry", "control_family": "SIEM", "covered": False, + "detail": "No rule for this payload", "confidence": "medium"}, + ], + "rationale": "No coverage", + "evidence": [], +} +RED_TURN_ALT = { + "viability_score": 0.3, + "techniques_attempted": ["T1210"], + "failed_alternatives": [], + "rationale": "Switching to a different lateral movement technique", + "evidence": [], +} + + +def test_selects_only_scenarios_above_viability_threshold(): + low = make_scenario(path_score=0.5, detection_coverage=0.9, risk_level="LOW") + high = make_scenario(path_score=4.5, detection_coverage=0.0, risk_level="CRITICAL") + engine = RedBlueDebateEngine(FakeProvider([]), config={"min_viability_threshold": 0.5}) + selected = engine._select_scenarios([low, high]) + assert selected == [high] + + +def test_top_n_caps_selection(): + scenarios = [make_scenario(path_score=4.0 + i, detection_coverage=0.0) for i in range(10)] + engine = RedBlueDebateEngine(FakeProvider([]), config={"top_n": 3, "min_viability_threshold": 0.0}) + selected = engine._select_scenarios(scenarios) + assert len(selected) == 3 + assert selected[0].path_score == max(s.path_score for s in scenarios) + + +def test_convergence_stops_before_max_rounds(): + scenario = make_scenario() + provider = FakeProvider([RED_TURN_HIGH, BLUE_TURN_BLOCK_ALL]) + engine = RedBlueDebateEngine(provider, config={"max_rounds": 3, "viability_delta_threshold": 0.5}) + result = asyncio.run(engine._debate_scenario(scenario)) + assert result is not None + assert result.converged is True + assert len(result.rounds) == 2 + + +def test_max_rounds_respected_when_not_converging(): + scenario = make_scenario() + # Viability must genuinely keep moving round to round — identical canned turns would + # converge on repetition (delta hits an exact 0.0 fixed point), which is not what this + # test is exercising. Alternating RED_TURN_HIGH/RED_TURN_ALT keeps the delta above + # threshold every round. + provider = FakeProvider([ + RED_TURN_HIGH, BLUE_TURN_GAP, + RED_TURN_ALT, BLUE_TURN_GAP, + RED_TURN_HIGH, BLUE_TURN_GAP, + ]) + engine = RedBlueDebateEngine(provider, config={"max_rounds": 3, "viability_delta_threshold": 0.001}) + result = asyncio.run(engine._debate_scenario(scenario)) + assert result is not None + assert len(result.rounds) == 6 + assert result.converged is False + + +def test_malformed_turn_json_skipped_gracefully(): + scenario = make_scenario() + provider = FakeProvider([{"unexpected": "shape"}]) + engine = RedBlueDebateEngine(provider, config={"max_rounds": 3}) + result = asyncio.run(engine._debate_scenario(scenario)) + assert result is None + + +def test_debate_factor_bounded_and_risk_level_recomputed(): + scenario = make_scenario(path_score=4.5, risk_level="CRITICAL") + provider = FakeProvider([RED_TURN_HIGH, BLUE_TURN_BLOCK_ALL]) + # BLUE_TURN_BLOCK_ALL only blocks 1 technique, so the round viability (0.8 - 0.15*1 = 0.65) + # stays above the engine's default min_viability_threshold (0.5) — residual_path_viable would + # be True and the >1.0 cap would never engage. Raising the threshold to 0.7 for this test + # makes 0.65 register as "not viable," which is what "Blue blocked everything -> capped at + # 1.0" is meant to exercise. + engine = RedBlueDebateEngine( + provider, + config={"max_rounds": 1, "debate_factor_min": 0.5, "debate_factor_max": 1.5, + "viability_delta_threshold": 0.5, "min_viability_threshold": 0.7}, + ) + results = asyncio.run(engine.run([scenario])) + assert len(results) == 1 + assert scenario.path_score_pre_debate == 4.5 + assert 0.5 <= scenario.debate_factor <= 1.5 + assert scenario.path_score <= 4.5 # Blue blocked everything -> factor capped at 1.0 + assert scenario.risk_level in {"CRITICAL", "HIGH", "MEDIUM", "LOW"} + + +def test_evidence_without_ref_marked_unverified(): + scenario = make_scenario() + turn_with_bad_evidence = dict(RED_TURN_HIGH) + turn_with_bad_evidence["evidence"] = [ + {"claim": "unsupported claim", "evidence_type": "none", "evidence_ref": "", "confidence": "low"}, + ] + provider = FakeProvider([turn_with_bad_evidence, BLUE_TURN_BLOCK_ALL]) + engine = RedBlueDebateEngine(provider, config={"max_rounds": 1}) + result = asyncio.run(engine._debate_scenario(scenario)) + assert result is not None + red_turn = result.rounds[0] + assert red_turn.evidence[0].verified is False + + +def test_grounding_includes_hop_facts_and_bom_cves(tmp_path): + bom_dir = tmp_path / "BOM" + bom_dir.mkdir() + (bom_dir / "webserver.yaml").write_text("known_cves:\n - CVE-2024-1234\n", encoding="utf-8") + scenario = make_scenario() + engine = RedBlueDebateEngine(FakeProvider([]), bom_directory=str(bom_dir)) + grounding = engine._build_grounding(scenario) + assert "unauthenticated" in grounding + assert "https" in grounding + assert "CVE-2024-1234" in grounding + + +from threat_analysis.generation.report_generator import ReportGenerator + + +def test_run_debate_disabled_by_default(): + rg = ReportGenerator(MagicMock(), MagicMock()) + rg.ai_provider = MagicMock() + rg._debate_config = {} + threat_model = MagicMock() + threat_model.gdaf_scenarios = [make_scenario()] + result = asyncio.run(rg._run_debate(threat_model)) + assert result == [] + + +def test_run_debate_no_provider(): + rg = ReportGenerator(MagicMock(), MagicMock()) + rg.ai_provider = None + rg._debate_config = {"enabled": True} + threat_model = MagicMock() + threat_model.gdaf_scenarios = [make_scenario()] + result = asyncio.run(rg._run_debate(threat_model)) + assert result == [] + + +def test_run_debate_no_scenarios(): + rg = ReportGenerator(MagicMock(), MagicMock()) + rg.ai_provider = MagicMock() + rg._debate_config = {"enabled": True} + threat_model = MagicMock() + threat_model.gdaf_scenarios = [] + result = asyncio.run(rg._run_debate(threat_model)) + assert result == [] + + +def test_run_debate_runs_engine_when_enabled_and_online(): + rg = ReportGenerator(MagicMock(), MagicMock()) + mock_client = MagicMock() + mock_client.ai_online = True + rg.ai_provider = MagicMock() + rg.ai_provider._get_client = AsyncMock(return_value=mock_client) + rg.ai_provider.generate_debate_turn = AsyncMock(side_effect=[RED_TURN_HIGH, BLUE_TURN_BLOCK_ALL]) + rg._debate_config = {"enabled": True, "max_rounds": 1, "viability_delta_threshold": 0.5} + threat_model = MagicMock() + threat_model.gdaf_scenarios = [make_scenario()] + threat_model.context_config = {} + threat_model._model_file_path = None + result = asyncio.run(rg._run_debate(threat_model)) + assert len(result) == 1 + assert result[0].scenario_id == "GDAF-TEST01" + + +import re +import jinja2 +from pathlib import Path + +_TEMPLATE_PATH = Path(__file__).resolve().parents[1] / "threat_analysis" / "templates" / "report_template.html" + + +def _extract_debate_section() -> str: + text = _TEMPLATE_PATH.read_text(encoding="utf-8") + match = re.search( + r"(.*?)", + text, re.DOTALL, + ) + assert match, "DEBATE_SECTION markers not found in report_template.html" + return match.group(1) + + +def test_debate_section_hidden_when_no_results(): + tmpl = jinja2.Template(_extract_debate_section()) + html = tmpl.render(debate_results=[]) + assert "Red/Blue Adversarial Debate" not in html + + +def test_debate_section_shows_persuasion_order(): + tmpl = jinja2.Template(_extract_debate_section()) + debate_results = [{ + "objective_name": "Exfiltrate data", + "entry_point": "ExternalUser", + "target_asset": "WebServer", + "residual_path_viable": True, + "final_viability": 0.6, + "blocked_paths": ["T1078"], + "residual_detection_gaps": [ + {"step": "hop1", "control_family": "SIEM", "covered": False, + "detail": "no rule", "confidence": "medium"}, + ], + "red_failed_attempts": ["Tried T1110 — blocked"], + "round_count": 2, + "converged": True, + "convergence_delta": 0.05, + "debate_factor": 1.2, + "rounds": [], + }] + html = tmpl.render(debate_results=debate_results) + assert "Red/Blue Adversarial Debate" in html + idx_blocked = html.index("Paths Blue successfully blocked") + idx_gaps = html.index("Detection gaps on the residual path") + idx_failed = html.index("Red's failed alternative attempts") + idx_footnote = html.index("round(s)") + assert idx_blocked < idx_gaps < idx_failed < idx_footnote diff --git a/tests/test_gdaf_engine.py b/tests/test_gdaf_engine.py index 0df063a..45c3462 100644 --- a/tests/test_gdaf_engine.py +++ b/tests/test_gdaf_engine.py @@ -23,6 +23,7 @@ GDAFEngine, AttackScenario, AttackHop, + compute_risk_level, _CLASSIFICATION_SCORE, _TRAVERSAL_BONUS, _DETECTION_COVERAGE, @@ -1445,3 +1446,38 @@ def test_bfs_handles_disconnected_graph(self): graph = engine._build_graph() paths = engine._bfs_paths(graph, "AttackerA", "ServerB", max_hops=10) assert paths == [] + + +# --------------------------------------------------------------------------- +# compute_risk_level function and debate fields +# --------------------------------------------------------------------------- + +class TestComputeRiskLevel: + def test_compute_risk_level_thresholds(self): + thresholds = {"CRITICAL": 4.0, "HIGH": 2.8, "MEDIUM": 1.8} + assert compute_risk_level(4.5, thresholds) == "CRITICAL" + assert compute_risk_level(4.0, thresholds) == "CRITICAL" + assert compute_risk_level(3.0, thresholds) == "HIGH" + assert compute_risk_level(2.0, thresholds) == "MEDIUM" + assert compute_risk_level(1.0, thresholds) == "LOW" + + +class TestGDAFEngineGetRiskThresholds: + def test_get_risk_thresholds_defaults(self): + GDAFEngine._scoring_config = None # reset class-level cache + with patch.object(GDAFEngine, "_load_scoring_config", return_value={}): + thresholds = GDAFEngine.get_risk_thresholds() + assert thresholds == {"CRITICAL": 4.0, "HIGH": 2.8, "MEDIUM": 1.8} + + +class TestAttackScenarioDebateFields: + def test_attack_scenario_debate_fields_default(self): + scenario = AttackScenario( + scenario_id="s1", objective_id="o1", objective_name="Obj", + objective_description="", objective_business_impact="", objective_mitre_final_tactic="", + actor_id="a1", actor_name="Actor", actor_sophistication="medium", + entry_point="Entry", target_asset="Target", hops=[], path_score=3.0, + risk_level="HIGH", detection_coverage=0.5, unacceptable_risk=False, + ) + assert scenario.path_score_pre_debate is None + assert scenario.debate_factor == 1.0 diff --git a/tests/test_litellm_provider.py b/tests/test_litellm_provider.py index a70e786..bb83efb 100644 --- a/tests/test_litellm_provider.py +++ b/tests/test_litellm_provider.py @@ -285,3 +285,17 @@ async def mock_gen(**kwargs): result = await provider.generate_attack_flow({}, {}, {}) assert result == {"flow": "steps"} asyncio.run(_run()) + +def test_litellm_provider_generate_debate_turn(): + async def _run(): + with patch("threat_analysis.ai_engine.providers.litellm_client.LiteLLMClient.create", new_callable=AsyncMock) as mock_create: + mock_client = MagicMock() + async def mock_gen(**kwargs): + yield {"viability_score": 0.6, "techniques_attempted": ["T1190"]} + mock_client.generate_content = mock_gen + mock_create.return_value = mock_client + + provider = LiteLLMProvider({}) + result = await provider.generate_debate_turn("prompt", "system") + assert result == {"viability_score": 0.6, "techniques_attempted": ["T1190"]} + asyncio.run(_run()) diff --git a/tests/test_server.py b/tests/test_server.py index 9d790eb..3e0ba75 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -36,6 +36,7 @@ def client(): app.config['TESTING'] = True # Reset module-level globals so tests don't bleed state into each other _server_module.initial_model_file_path = None + _server_module.graphical_editor_enabled = False with app.test_client() as client: yield client @@ -294,6 +295,7 @@ def test_save_model_missing_markdown(client): def test_graphical_update_success(client): """Test the /api/graphical_update endpoint with valid JSON data.""" + _server_module.graphical_editor_enabled = True with patch('threat_analysis.server.server.convert_json_to_markdown') as mock_convert, \ patch('threat_analysis.server.server.get_threat_model_service') as mock_get_service: @@ -312,6 +314,7 @@ def test_graphical_update_success(client): def test_graphical_update_empty_json(client): """Test the /api/graphical_update endpoint with empty JSON data.""" + _server_module.graphical_editor_enabled = True response = client.post('/api/graphical_update', data=json.dumps({}), content_type='application/json') assert response.status_code == 400 assert 'JSON data is empty' in response.get_json()['error'] @@ -479,6 +482,11 @@ def test_get_model_name_function(): markdown_whitespace = "# Threat Model: Test Model \n## Description\nTest" assert get_model_name(markdown_whitespace) == "Test Model" + # "System Model:" prefix is also accepted (terminology: the DSL file describes + # the system model; "Threat Model" is reserved for the analysis output). + markdown_system_model = "# System Model: My Test Model\n## Description\nTest" + assert get_model_name(markdown_system_model) == "My Test Model" + def test_convert_json_to_markdown_function(): """Test the convert_json_to_markdown function.""" @@ -505,7 +513,7 @@ def test_convert_json_to_markdown_function(): markdown = convert_json_to_markdown(json_data) # Check that all sections are present - assert '# Threat Model: Graphical Editor' in markdown + assert '# System Model: Graphical Editor' in markdown assert '## Boundaries' in markdown assert '## Actors' in markdown assert '## Servers' in markdown @@ -532,13 +540,30 @@ def test_simple_mode_route(client): # Check that the initial markdown is base64 encoded in the response assert b'Threat Model Editor' in response.data -def test_graphical_editor_route(client): - """Test the /graphical route.""" +def test_graphical_editor_route_disabled_by_default(client): + """The /graphical route is disabled unless explicitly enabled.""" + response = client.get('/graphical') + assert response.status_code == 404 + + +def test_graphical_editor_route_enabled(client): + """The /graphical route serves the editor once explicitly enabled.""" + _server_module.graphical_editor_enabled = True response = client.get('/graphical') assert response.status_code == 200 assert b'Graphical Editor' in response.data or b'threat-model' in response.data +def test_index_route_hides_graphical_editor_link_by_default(client): + """The main menu only links to the Graphical Editor when it is enabled.""" + response = client.get('/') + assert b'/graphical' not in response.data + + _server_module.graphical_editor_enabled = True + response = client.get('/') + assert b'/graphical' in response.data + + def test_static_files_route(client): """Test the /static/ route.""" # This is a basic test - in a real scenario, you'd need to ensure the static files exist @@ -1143,6 +1168,7 @@ def test_update_api_value_error_from_service(client): def test_graphical_update_exception(client): """Unhandled exception in /api/graphical_update → 500 (lines 598-600).""" + _server_module.graphical_editor_enabled = True with patch('threat_analysis.server.server.convert_json_to_markdown') as mock_convert, \ patch('threat_analysis.server.server.get_threat_model_service') as mock_get: mock_svc = MagicMock() diff --git a/threatModel_Template/Advanced_Cloud_WebApp/model.md b/threatModel_Template/Advanced_Cloud_WebApp/model.md index 04ebbcf..b0ec8ff 100644 --- a/threatModel_Template/Advanced_Cloud_WebApp/model.md +++ b/threatModel_Template/Advanced_Cloud_WebApp/model.md @@ -3,7 +3,7 @@ Copyright 2024 ellipse2v — Apache License, Version 2.0 https://www.apache.org/licenses/LICENSE-2.0 --> -# Threat Model: Advanced Cloud Web Application +# System Model: Advanced Cloud Web Application ## Description Advanced cloud web app with CDN, API gateway, auth service, product microservice, and managed database. diff --git a/threatModel_Template/CI_CD_Pipeline/model.md b/threatModel_Template/CI_CD_Pipeline/model.md index d31882b..14a9bc8 100644 --- a/threatModel_Template/CI_CD_Pipeline/model.md +++ b/threatModel_Template/CI_CD_Pipeline/model.md @@ -3,7 +3,7 @@ Copyright 2024 ellipse2v — Apache License, Version 2.0 https://www.apache.org/licenses/LICENSE-2.0 --> -# Threat Model: CI/CD Pipeline (Continuous Integration and Deployment) +# System Model: CI/CD Pipeline (Continuous Integration and Deployment) ## Description This threat model focuses on a CI/CD pipeline, covering the development, integration, testing, and deployment stages. diff --git a/threatModel_Template/Cloud_Native_Architecture/model.md b/threatModel_Template/Cloud_Native_Architecture/model.md index 762d36b..b91abf6 100644 --- a/threatModel_Template/Cloud_Native_Architecture/model.md +++ b/threatModel_Template/Cloud_Native_Architecture/model.md @@ -3,7 +3,7 @@ Copyright 2024 ellipse2v — Apache License, Version 2.0 https://www.apache.org/licenses/LICENSE-2.0 --> -# Threat Model: Cloud-Native Architecture +# System Model: Cloud-Native Architecture ## Description This threat model addresses cloud-native architectures, focusing on serverless functions, API gateways, managed databases, and object storage. diff --git a/threatModel_Template/Data_Warehouse_Data_Lake/model.md b/threatModel_Template/Data_Warehouse_Data_Lake/model.md index 5443440..46bc3ca 100644 --- a/threatModel_Template/Data_Warehouse_Data_Lake/model.md +++ b/threatModel_Template/Data_Warehouse_Data_Lake/model.md @@ -1,4 +1,4 @@ -# Threat Model: Data Warehouse / Data Lake Architecture +# System Model: Data Warehouse / Data Lake Architecture ## Description Data warehouse and data lake architecture with ETL pipelines, object storage, data lakehouse, and BI tools. diff --git a/threatModel_Template/IP_Camera_Surveillance/model.md b/threatModel_Template/IP_Camera_Surveillance/model.md index ee77cab..cd7dc51 100644 --- a/threatModel_Template/IP_Camera_Surveillance/model.md +++ b/threatModel_Template/IP_Camera_Surveillance/model.md @@ -1,4 +1,4 @@ -# Threat Model: IP Camera Surveillance System +# System Model: IP Camera Surveillance System ## Description IP-based video surveillance system for a commercial building: fixed and PTZ cameras, diff --git a/threatModel_Template/IoT/model.md b/threatModel_Template/IoT/model.md index f4b8a2b..1386d30 100644 --- a/threatModel_Template/IoT/model.md +++ b/threatModel_Template/IoT/model.md @@ -1,4 +1,4 @@ -# Threat Model: IoT Architecture +# System Model: IoT Architecture ## Description IoT system with sensors, gateway, cloud platform, analytics, and device management. diff --git a/threatModel_Template/Kubernetes_Helm_Cluster/model.md b/threatModel_Template/Kubernetes_Helm_Cluster/model.md index 89c639e..ab78dd8 100644 --- a/threatModel_Template/Kubernetes_Helm_Cluster/model.md +++ b/threatModel_Template/Kubernetes_Helm_Cluster/model.md @@ -1,4 +1,4 @@ -# Threat Model: Kubernetes / Helm Cluster +# System Model: Kubernetes / Helm Cluster ## Description Production Kubernetes cluster deployed via Helm, with three application namespaces diff --git a/threatModel_Template/Microservices_Architecture/model.md b/threatModel_Template/Microservices_Architecture/model.md index e887723..086bae8 100644 --- a/threatModel_Template/Microservices_Architecture/model.md +++ b/threatModel_Template/Microservices_Architecture/model.md @@ -3,7 +3,7 @@ Copyright 2024 ellipse2v — Apache License, Version 2.0 https://www.apache.org/licenses/LICENSE-2.0 --> -# Threat Model: Microservices Architecture +# System Model: Microservices Architecture ## Description This threat model covers a microservices-based application with an API gateway, service mesh, message broker, and shared database. diff --git a/threatModel_Template/Mobile_Application/model.md b/threatModel_Template/Mobile_Application/model.md index 91117c2..bb87a58 100644 --- a/threatModel_Template/Mobile_Application/model.md +++ b/threatModel_Template/Mobile_Application/model.md @@ -1,4 +1,4 @@ -# Threat Model: Mobile Application +# System Model: Mobile Application ## Description Mobile application with backend API, third-party identity provider, and payment gateway integration. diff --git a/threatModel_Template/On-Prem_Enterprise_Network/model.md b/threatModel_Template/On-Prem_Enterprise_Network/model.md index 876e8c8..d8e11e1 100644 --- a/threatModel_Template/On-Prem_Enterprise_Network/model.md +++ b/threatModel_Template/On-Prem_Enterprise_Network/model.md @@ -1,4 +1,4 @@ -# Threat Model: On-Prem Enterprise Network +# System Model: On-Prem Enterprise Network ## Description This model describes a complex on-premises enterprise network for a large multinational manufacturing organization. It covers 9 security zones ranging from an untrusted internet perimeter to a highly restricted OT/SCADA environment. The architecture includes Active Directory infrastructure, a financial ERP system, a CI/CD development pipeline, and legacy industrial control systems. This model highlights threats related to lateral movement, credential theft, legacy protocols, and OT network security. diff --git a/threatModel_Template/Satellite_Ground_Segment/model.md b/threatModel_Template/Satellite_Ground_Segment/model.md index 5f6c32c..419d986 100644 --- a/threatModel_Template/Satellite_Ground_Segment/model.md +++ b/threatModel_Template/Satellite_Ground_Segment/model.md @@ -1,4 +1,4 @@ -# Threat Model: Satellite Ground Segment (Thales Demo Scenario) +# System Model: Satellite Ground Segment (Thales Demo Scenario) ## Description Low Earth Orbit satellite system modelling the attack surface demonstrated by Thales diff --git a/threatModel_Template/Serverless_AWS_Lambda/model.md b/threatModel_Template/Serverless_AWS_Lambda/model.md index 1ef2407..ed108b5 100644 --- a/threatModel_Template/Serverless_AWS_Lambda/model.md +++ b/threatModel_Template/Serverless_AWS_Lambda/model.md @@ -1,4 +1,4 @@ -# Threat Model: Serverless — AWS Lambda Event-Driven Architecture +# System Model: Serverless — AWS Lambda Event-Driven Architecture ## Description Event-driven serverless application on AWS. External clients reach the system via diff --git a/threatModel_Template/Simple_Monolithic_Web_Application/model.md b/threatModel_Template/Simple_Monolithic_Web_Application/model.md index 70c8efb..4809d57 100644 --- a/threatModel_Template/Simple_Monolithic_Web_Application/model.md +++ b/threatModel_Template/Simple_Monolithic_Web_Application/model.md @@ -1,4 +1,4 @@ -# Threat Model: Simple Monolithic Web Application +# System Model: Simple Monolithic Web Application ## Description A monolithic web application with a single application server handling all business logic. diff --git a/threatModel_Template/Three_Tier_Architecture/model.md b/threatModel_Template/Three_Tier_Architecture/model.md index 5a35a3d..5541c2e 100644 --- a/threatModel_Template/Three_Tier_Architecture/model.md +++ b/threatModel_Template/Three_Tier_Architecture/model.md @@ -3,7 +3,7 @@ Copyright 2024 ellipse2v — Apache License, Version 2.0 https://www.apache.org/licenses/LICENSE-2.0 --> -# Threat Model: Three-Tier Architecture +# System Model: Three-Tier Architecture ## Description Classic three-tier web architecture: presentation, business logic, and data. diff --git a/threatModel_Template/Traditional_Enterprise_Network/model.md b/threatModel_Template/Traditional_Enterprise_Network/model.md index b25f751..80b0369 100644 --- a/threatModel_Template/Traditional_Enterprise_Network/model.md +++ b/threatModel_Template/Traditional_Enterprise_Network/model.md @@ -1,4 +1,4 @@ -# Threat Model: Traditional Enterprise Network +# System Model: Traditional Enterprise Network ## Description Traditional enterprise with Active Directory, DMZ, file servers, and application servers. diff --git a/threatModel_Template/UAV_Drone_System/model.md b/threatModel_Template/UAV_Drone_System/model.md index 61c15ae..70d4c58 100644 --- a/threatModel_Template/UAV_Drone_System/model.md +++ b/threatModel_Template/UAV_Drone_System/model.md @@ -1,4 +1,4 @@ -# Threat Model: DJI Power Inspection Drone System +# System Model: DJI Power Inspection Drone System ## Description diff --git a/threatModel_Template/Zero_Trust/model.md b/threatModel_Template/Zero_Trust/model.md index 63e09c7..f7d3c88 100644 --- a/threatModel_Template/Zero_Trust/model.md +++ b/threatModel_Template/Zero_Trust/model.md @@ -3,7 +3,7 @@ Copyright 2024 ellipse2v — Apache License, Version 2.0 https://www.apache.org/licenses/LICENSE-2.0 --> -# Threat Model: Zero Trust Architecture +# System Model: Zero Trust Architecture ## Description This threat model describes a zero trust network architecture with identity-based access control. diff --git a/threatModel_Template/projects/example_3/api_gateway/model.md b/threatModel_Template/projects/example_3/api_gateway/model.md index 0df30d0..b5f6b3b 100644 --- a/threatModel_Template/projects/example_3/api_gateway/model.md +++ b/threatModel_Template/projects/example_3/api_gateway/model.md @@ -1,4 +1,4 @@ -# Threat Model: EcoShop — API Gateway Cluster +# System Model: EcoShop — API Gateway Cluster ## Description The API gateway cluster consists of a load balancer distributing requests across two Kong diff --git a/threatModel_Template/projects/example_3/backend/database/model.md b/threatModel_Template/projects/example_3/backend/database/model.md index fdfa32e..795c60f 100644 --- a/threatModel_Template/projects/example_3/backend/database/model.md +++ b/threatModel_Template/projects/example_3/backend/database/model.md @@ -1,4 +1,4 @@ -# Threat Model: EcoShop — Database Cluster +# System Model: EcoShop — Database Cluster ## Description The database cluster consists of a primary PostgreSQL instance and a streaming replica. diff --git a/threatModel_Template/projects/example_3/backend/model.md b/threatModel_Template/projects/example_3/backend/model.md index 7bc2276..394ff2e 100644 --- a/threatModel_Template/projects/example_3/backend/model.md +++ b/threatModel_Template/projects/example_3/backend/model.md @@ -1,4 +1,4 @@ -# Threat Model: EcoShop — Backend Services Tier +# System Model: EcoShop — Backend Services Tier ## Description The backend services tier hosts the core business microservices: an authentication service, diff --git a/threatModel_Template/projects/example_3/dmz/model.md b/threatModel_Template/projects/example_3/dmz/model.md index ac6b0db..d8afc4f 100644 --- a/threatModel_Template/projects/example_3/dmz/model.md +++ b/threatModel_Template/projects/example_3/dmz/model.md @@ -1,4 +1,4 @@ -# Threat Model: EcoShop — DMZ Tier +# System Model: EcoShop — DMZ Tier ## Description The DMZ contains two redundant hardware firewalls (active/passive HA pair) and an API diff --git a/threatModel_Template/projects/example_3/frontend/model.md b/threatModel_Template/projects/example_3/frontend/model.md index af6dda7..2fdf1da 100644 --- a/threatModel_Template/projects/example_3/frontend/model.md +++ b/threatModel_Template/projects/example_3/frontend/model.md @@ -1,4 +1,4 @@ -# Threat Model: EcoShop — Frontend Tier +# System Model: EcoShop — Frontend Tier ## Description The frontend tier consists of an nginx web server delivering the React SPA and a HAProxy diff --git a/threatModel_Template/projects/example_3/main.md b/threatModel_Template/projects/example_3/main.md index 2b9c2e4..8188c11 100644 --- a/threatModel_Template/projects/example_3/main.md +++ b/threatModel_Template/projects/example_3/main.md @@ -1,4 +1,4 @@ -# Threat Model: EcoShop — E-commerce Platform (Overview) +# System Model: EcoShop — E-commerce Platform (Overview) ## Description EcoShop is a B2C e-commerce platform serving EU and US customers. The architecture follows diff --git a/threatModel_Template/projects/example_3/order_service/model.md b/threatModel_Template/projects/example_3/order_service/model.md index e3ebac7..e81e72e 100644 --- a/threatModel_Template/projects/example_3/order_service/model.md +++ b/threatModel_Template/projects/example_3/order_service/model.md @@ -1,4 +1,4 @@ -# Threat Model: EcoShop — Order Service Cluster +# System Model: EcoShop — Order Service Cluster ## Description The Order Service is an event-driven microservices cluster responsible for the full order diff --git a/threatModel_Template/projects/example_4/card_vault/model.md b/threatModel_Template/projects/example_4/card_vault/model.md index 07fd409..00f215c 100644 --- a/threatModel_Template/projects/example_4/card_vault/model.md +++ b/threatModel_Template/projects/example_4/card_vault/model.md @@ -1,4 +1,4 @@ -# Threat Model: PayFlow — Card Data Vault +# System Model: PayFlow — Card Data Vault ## Description The card data vault is the most sensitive component in the PayFlow architecture. A TokenProxy diff --git a/threatModel_Template/projects/example_4/fraud_engine/model.md b/threatModel_Template/projects/example_4/fraud_engine/model.md index e952c02..8fa010a 100644 --- a/threatModel_Template/projects/example_4/fraud_engine/model.md +++ b/threatModel_Template/projects/example_4/fraud_engine/model.md @@ -1,4 +1,4 @@ -# Threat Model: PayFlow — Fraud Detection Engine +# System Model: PayFlow — Fraud Detection Engine ## Description The fraud detection engine provides real-time payment risk scoring with a latency SLA of diff --git a/threatModel_Template/projects/example_4/main.md b/threatModel_Template/projects/example_4/main.md index 7b603a1..bfcba9c 100644 --- a/threatModel_Template/projects/example_4/main.md +++ b/threatModel_Template/projects/example_4/main.md @@ -1,4 +1,4 @@ -# Threat Model: PayFlow — Cloud-Native Fintech Payment Platform +# System Model: PayFlow — Cloud-Native Fintech Payment Platform ## Description PayFlow is a PCI-DSS Level 1 certified payment processing platform serving EU and US markets. diff --git a/threatModel_Template/projects/example_4/payment_api/model.md b/threatModel_Template/projects/example_4/payment_api/model.md index 0390bb1..f81dd4b 100644 --- a/threatModel_Template/projects/example_4/payment_api/model.md +++ b/threatModel_Template/projects/example_4/payment_api/model.md @@ -1,4 +1,4 @@ -# Threat Model: PayFlow — Payment API Services +# System Model: PayFlow — Payment API Services ## Description The payment API tier contains the core payment processing microservices behind the Kong reverse diff --git a/threatModel_Template/threat_model.md b/threatModel_Template/threat_model.md index d3195dc..aa9542c 100644 --- a/threatModel_Template/threat_model.md +++ b/threatModel_Template/threat_model.md @@ -1,4 +1,4 @@ -# Threat Model: Advanced DMZ Architecture +# System Model: Advanced DMZ Architecture ## Description This model describes a network architecture with a Demilitarized Zone (DMZ), external and internal dataflows, a scalable gateway, and a potentially untrusted command zone. The goal is to identify STRIDE threats and map them to MITRE ATT&CK techniques. diff --git a/threat_analysis/__main__.py b/threat_analysis/__main__.py index dc59606..37883af 100644 --- a/threat_analysis/__main__.py +++ b/threat_analysis/__main__.py @@ -587,7 +587,13 @@ def __init__(self, loaded_plugins: Dict[str, 'IaCPlugin']): common.add_argument( "--server", action="store_true", - help="Launch the web editor (Monaco + graphical canvas).", + help="Launch the web editor (Monaco editor, Simple Mode).", + ) + common.add_argument( + "--graphical-editor", + action="store_true", + dest="graphical_editor", + help="Enable the experimental drag-and-drop Graphical Editor mode (disabled by default; use with --server).", ) common.add_argument( "--port", @@ -1146,7 +1152,11 @@ def main(): if getattr(args, "port", None): os.environ["FLASK_PORT"] = str(args.port) try: - run_server(model_filepath=args.model_file, project_path=args.project) + run_server( + model_filepath=args.model_file, + project_path=args.project, + enable_graphical_editor=args.graphical_editor, + ) except ImportError: logging.error( f"[{time.time() - _start_time_main:.4f}s] ❌ Flask is not installed. Please install it to use the web server: " diff --git a/threat_analysis/ai_engine/providers/base_provider.py b/threat_analysis/ai_engine/providers/base_provider.py index 4588361..7b6c03e 100644 --- a/threat_analysis/ai_engine/providers/base_provider.py +++ b/threat_analysis/ai_engine/providers/base_provider.py @@ -97,3 +97,26 @@ async def generate_soc_analysis( ``siem_rules``, ``iocs``. """ return [] + + async def generate_debate_turn( + self, + prompt: str, + system_prompt: str, + ) -> Dict: + """Generates one Red or Blue turn in an adversarial debate round. + + Optional — providers that do not override this return an empty dict, + which causes RedBlueDebateEngine to skip the turn gracefully. + + Args: + prompt: Role-specific user prompt (grounding + prior turn summary). + system_prompt: Red or Blue persona system instruction from prompts.yaml. + + Returns: + A dict with keys: ``viability_score`` and role-specific keys + (``techniques_attempted``/``failed_alternatives`` for Red, + ``techniques_blocked``/``detection_gaps`` for Blue), plus + ``rationale`` and ``evidence``. Returns ``{}`` on failure or when + not overridden. + """ + return {} diff --git a/threat_analysis/ai_engine/providers/litellm_provider.py b/threat_analysis/ai_engine/providers/litellm_provider.py index c3d3a56..6620204 100644 --- a/threat_analysis/ai_engine/providers/litellm_provider.py +++ b/threat_analysis/ai_engine/providers/litellm_provider.py @@ -216,6 +216,22 @@ async def generate_soc_analysis(self, batch_prompt: str, system_prompt: str) -> logging.error("SOC analysis generation failed: %s", exc) return [] + async def generate_debate_turn(self, prompt: str, system_prompt: str) -> Dict: + """Calls the LLM with a Red or Blue debate persona and returns the parsed turn.""" + client = await self._get_client() + try: + async for chunk in client.generate_content( + prompt=prompt, + system_prompt=system_prompt, + output_format="json", + ): + if isinstance(chunk, dict): + return chunk + return {} + except Exception as exc: + logging.error("Red/Blue debate turn generation failed: %s", exc) + return {} + async def generate_markdown( self, prompt: str, diff --git a/threat_analysis/config/prompts.yaml b/threat_analysis/config/prompts.yaml index c5709ba..ee386f3 100644 --- a/threat_analysis/config/prompts.yaml +++ b/threat_analysis/config/prompts.yaml @@ -16,11 +16,11 @@ dsl_generation: system: | You are an expert cybersecurity architect specializing in STRIDE threat modeling. - Your task is to produce a complete, valid threat model in the SecOpsTM Markdown DSL. + Your task is to produce a complete, valid system model in the SecOpsTM Markdown DSL. ## DSL Structure - # Threat Model: [Name] + # System Model: [Name] ## Description [Concise description of the system and its security context] @@ -412,7 +412,7 @@ rag: human_template: | {optional_context} - ## Threat Model (Architecture) + ## System Model (Architecture) {threat_model_markdown} ## Retrieved Security Knowledge (CVE / CAPEC patterns) @@ -589,3 +589,105 @@ ciso_triage: ], "narrative": "{3-5 sentence executive summary for board presentation}" } + +# --------------------------------------------------------------------------- +# 7. RED/BLUE ADVERSARIAL DEBATE +# Used by: RedBlueDebateEngine / LiteLLMProvider.generate_debate_turn +# Purpose: Multi-round attack/defence debate over top GDAF attack scenarios. +# Output (red): viability_score, techniques_attempted, failed_alternatives, +# rationale, evidence[] +# Output (blue): viability_score, techniques_blocked, detection_gaps[], +# rationale, evidence[] +# --------------------------------------------------------------------------- +red_blue_debate: + red_system: | + You are a senior red team operator with deep expertise in MITRE ATT&CK techniques, + exploitation of misconfigurations, and adversarial path-finding. + + Rules: + - Only use facts explicitly listed in the grounding block you are given — never invent + a CVE, port, protocol, or misconfiguration that is not present there. + - Every claim you make must cite a specific piece of grounding evidence (CVE, misconfig, + port/service, or privilege boundary). + - If Blue's prior response blocks your previous approach, try a genuinely different + technique or hop. If none works, record the attempt under failed_alternatives — + this is valuable signal, not a failure to hide. + - viability_score reflects how likely THIS round's attempt is to succeed end-to-end, + from 0.0 (fully blocked) to 1.0 (fully viable). + + blue_system: | + You are a senior blue team / detection engineer with deep expertise in SIEM correlation + rules, EDR behavioral detection, IDS signatures, and MITRE D3FEND countermeasures. + + Rules: + - Only use facts explicitly listed in the grounding block you are given — never invent + a control that is not implied by it. + - For each Red technique, decide whether it is blocked/detected or not, and name the + specific control family: "SIEM", "EDR", or "IDS". + - detection_gaps lists steps you CANNOT cover, with covered=false and a specific reason. + - Every claim you make must cite a specific piece of grounding evidence. + - viability_score reflects your own estimate of the residual attack viability after your + response, from 0.0 (fully blocked) to 1.0 (fully viable). + + red_round_user: | + ## Attack Scenario + Objective: <> + Entry point: <> + Target asset: <> + + ## Grounding facts (use these — do not invent facts not listed here) + <> + + ## Blue team's prior response + <> + + --- + Attempt to compromise the target through this path. Return ONLY a JSON object, + no markdown fence, no preamble: + { + "viability_score": 0.7, + "techniques_attempted": ["T1190", "T1078"], + "failed_alternatives": ["Attempted T1110 password spraying — blocked by account lockout policy"], + "rationale": "short justification", + "evidence": [ + { + "claim": "SSH endpoint accepts password auth", + "evidence_type": "misconfig", + "evidence_ref": "hop 2 dataflow: protocol=ssh, is_authenticated=false", + "confidence": "high" + } + ] + } + + blue_round_user: | + ## Grounding facts (use these — do not invent facts not listed here) + <> + + ## Red team's attack this round + <> + + --- + Assess which of Red's techniques you can detect or block, and identify detection gaps + for anything you cannot cover. Return ONLY a JSON object, no markdown fence, no preamble: + { + "viability_score": 0.4, + "techniques_blocked": ["T1078"], + "detection_gaps": [ + { + "step": "hop 2 SSH access", + "control_family": "SIEM", + "covered": false, + "detail": "No correlation rule for password-auth SSH from external IP ranges", + "confidence": "medium" + } + ], + "rationale": "short justification", + "evidence": [ + { + "claim": "EDR blocks known T1078 credential reuse pattern on this host", + "evidence_type": "privilege_boundary", + "evidence_ref": "hop 2 asset has mfa_enabled=true per grounding", + "confidence": "high" + } + ] + } diff --git a/threat_analysis/core/debate_engine.py b/threat_analysis/core/debate_engine.py new file mode 100644 index 0000000..a8519ab --- /dev/null +++ b/threat_analysis/core/debate_engine.py @@ -0,0 +1,370 @@ +# Copyright 2025 ellipse2v +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +RedBlueDebateEngine — adversarial Red/Blue debate over GDAF attack scenarios. + +A single configured LLM provider plays two personas (Red: attack, Blue: defence) +across multiple rounds on the top-N highest-viability AttackScenario objects. +The debate is grounded on facts already present in the model (hop protocol/ +auth/encryption, BOM CVEs) — no network calls beyond the existing LLM provider. + +The final viability adjusts the scenario's path_score/risk_level in place +(never the STRIDE threat table). All processing degrades to an empty result +when the provider is unavailable or returns malformed data — never raises. +""" + +import logging +from dataclasses import dataclass, field +from typing import Any, Dict, List, Optional + +from threat_analysis.ai_engine.prompt_loader import get as _get_prompt +from threat_analysis.core.gdaf_engine import GDAFEngine, compute_risk_level + +logger = logging.getLogger(__name__) + + +@dataclass +class Evidence: + claim: str + evidence_type: str # "cve" | "misconfig" | "port_service" | "privilege_boundary" | "none" + evidence_ref: str + confidence: str # "high" | "medium" | "low" + verified: bool + + +@dataclass +class DetectionGap: + step: str + control_family: str # "SIEM" | "EDR" | "IDS" | "NONE" + covered: bool + detail: str + confidence: str + + +@dataclass +class DebateTurn: + role: str # "red" | "blue" + round_index: int + viability_score: float + techniques_attempted: List[str] = field(default_factory=list) + techniques_blocked: List[str] = field(default_factory=list) + detection_gaps: List[DetectionGap] = field(default_factory=list) + failed_alternatives: List[str] = field(default_factory=list) + evidence: List[Evidence] = field(default_factory=list) + rationale: str = "" + + +@dataclass +class DebateResult: + scenario_id: str + objective_name: str + entry_point: str + target_asset: str + rounds: List[DebateTurn] + blocked_paths: List[str] + residual_detection_gaps: List[DetectionGap] + red_failed_attempts: List[str] + round_count: int + convergence_delta: float + converged: bool + final_viability: float + residual_path_viable: bool + debate_factor: float = 1.0 + + +def _clamp(value: float, low: float = 0.0, high: float = 1.0) -> float: + return max(low, min(high, value)) + + +class RedBlueDebateEngine: + """Runs the Red/Blue debate loop over GDAF AttackScenario objects. + + ``provider`` must implement ``async generate_debate_turn(prompt, system_prompt) -> Dict`` + (see BaseLLMProvider.generate_debate_turn). ``bom_directory`` is optional — grounding + falls back to hop protocol/auth/encryption facts only when no BOM directory is given. + """ + + def __init__( + self, + provider: Any, + config: Optional[Dict] = None, + bom_directory: Optional[str] = None, + ): + self.provider = provider + cfg = config or {} + self.top_n: int = int(cfg.get("top_n", 5)) + self.min_viability_threshold: float = float(cfg.get("min_viability_threshold", 0.5)) + self.max_rounds: int = int(cfg.get("max_rounds", 3)) + self.viability_delta_threshold: float = float(cfg.get("viability_delta_threshold", 0.1)) + self.debate_factor_min: float = float(cfg.get("debate_factor_min", 0.5)) + self.debate_factor_max: float = float(cfg.get("debate_factor_max", 1.5)) + self._bom_directory = bom_directory + + async def run(self, scenarios: List[Any]) -> List[DebateResult]: + """Debates the top-N eligible scenarios and mutates them in place with the outcome.""" + selected = self._select_scenarios(scenarios) + results: List[DebateResult] = [] + for scenario in selected: + result = await self._debate_scenario(scenario) + if result is None: + continue + self._reinject_score(scenario, result) + results.append(result) + return results + + # ------------------------------------------------------------------ + # Selection + # ------------------------------------------------------------------ + + def _select_scenarios(self, scenarios: List[Any]) -> List[Any]: + critical = GDAFEngine.get_risk_thresholds().get("CRITICAL", 4.0) + eligible = [ + s for s in scenarios + if self._initial_viability(s, critical) >= self.min_viability_threshold + ] + eligible.sort(key=lambda s: s.path_score, reverse=True) + return eligible[: self.top_n] + + @staticmethod + def _initial_viability(scenario: Any, critical_threshold: float) -> float: + detection = float(getattr(scenario, "detection_coverage", 0.0)) + raw = (scenario.path_score / critical_threshold) * (1.0 - 0.5 * detection) + return _clamp(raw) + + # ------------------------------------------------------------------ + # Grounding (evidence-first — facts already in the model, no new lookups) + # ------------------------------------------------------------------ + + def _build_grounding(self, scenario: Any) -> str: + bom_loader = None + if self._bom_directory: + from threat_analysis.core.bom_loader import BOMLoader # lazy: avoid cost when unused + bom_loader = BOMLoader(self._bom_directory) + + lines: List[str] = [] + for hop in getattr(scenario, "hops", []): + auth = "authenticated" if hop.is_authenticated else "unauthenticated" + enc = "encrypted" if hop.is_encrypted else "unencrypted" + techniques = ", ".join(t.id for t in hop.techniques[:3]) if hop.techniques else "none mapped" + cve_str = "no known CVEs on file" + if bom_loader: + bom_data = bom_loader.get(hop.asset_name) + cves = bom_data.get("known_cves") or [] + if cves: + cve_str = f"known CVEs: {', '.join(cves)}" + lines.append( + f"- {hop.asset_name} [{hop.hop_position}]: protocol={hop.protocol or 'unknown'}, " + f"{auth}, {enc}, candidate techniques: {techniques}; {cve_str}" + ) + return "\n".join(lines) if lines else "No hop data available." + + # ------------------------------------------------------------------ + # Debate loop + # ------------------------------------------------------------------ + + async def _debate_scenario(self, scenario: Any) -> Optional[DebateResult]: + grounding = self._build_grounding(scenario) + critical = GDAFEngine.get_risk_thresholds().get("CRITICAL", 4.0) + prev_viability = self._initial_viability(scenario, critical) + final_viability = prev_viability + + rounds: List[DebateTurn] = [] + blue_prior = "No prior Blue response yet — this is the first round." + converged = False + convergence_delta = 0.0 + + for round_index in range(self.max_rounds): + red_turn = await self._run_turn(scenario, "red", round_index, grounding, blue_prior) + if red_turn is None: + break + rounds.append(red_turn) + + blue_turn = await self._run_turn( + scenario, "blue", round_index, grounding, self._summarize_turn(red_turn) + ) + if blue_turn is None: + break + rounds.append(blue_turn) + + viability = _clamp(red_turn.viability_score - 0.15 * len(blue_turn.techniques_blocked)) + convergence_delta = abs(viability - prev_viability) + blue_prior = self._summarize_turn(blue_turn) + final_viability = viability + + if convergence_delta < self.viability_delta_threshold: + converged = True + prev_viability = viability + break + prev_viability = viability + + if not rounds: + return None + + return self._summarize_result(scenario, rounds, final_viability, converged, convergence_delta) + + async def _run_turn( + self, + scenario: Any, + role: str, + round_index: int, + grounding: str, + prior_summary: str, + ) -> Optional[DebateTurn]: + try: + if role == "red": + system_prompt = _get_prompt("red_blue_debate", "red_system") + user_prompt = _get_prompt( + "red_blue_debate", "red_round_user", + objective=scenario.objective_name, + entry_point=scenario.entry_point, + target_asset=scenario.target_asset, + grounding=grounding, + blue_prior=prior_summary, + ) + else: + system_prompt = _get_prompt("red_blue_debate", "blue_system") + user_prompt = _get_prompt( + "red_blue_debate", "blue_round_user", + grounding=grounding, + red_attack=prior_summary, + ) + except KeyError as exc: + logger.warning("Red/Blue debate: prompt key missing (%s) — skipping.", exc) + return None + + try: + raw = await self.provider.generate_debate_turn(user_prompt, system_prompt) + except Exception as exc: + logger.warning( + "Red/Blue debate: %s turn failed for scenario %s: %s", + role, getattr(scenario, "scenario_id", "?"), exc, + ) + return None + + if not isinstance(raw, dict) or "viability_score" not in raw: + logger.warning( + "Red/Blue debate: malformed %s turn for scenario %s — skipping", + role, getattr(scenario, "scenario_id", "?"), + ) + return None + + try: + return DebateTurn( + role=role, + round_index=round_index, + viability_score=_clamp(float(raw.get("viability_score", 0.0))), + techniques_attempted=list(raw.get("techniques_attempted") or []), + techniques_blocked=list(raw.get("techniques_blocked") or []), + detection_gaps=self._parse_detection_gaps(raw.get("detection_gaps")), + failed_alternatives=list(raw.get("failed_alternatives") or []), + evidence=self._parse_evidence(raw.get("evidence")), + rationale=str(raw.get("rationale", "")), + ) + except (TypeError, ValueError) as exc: + logger.warning("Red/Blue debate: could not parse %s turn: %s", role, exc) + return None + + @staticmethod + def _parse_evidence(raw_list: Any) -> List[Evidence]: + result: List[Evidence] = [] + for item in raw_list or []: + if not isinstance(item, dict): + continue + etype = str(item.get("evidence_type", "none")).lower() + ref = str(item.get("evidence_ref", "")) + result.append(Evidence( + claim=str(item.get("claim", "")), + evidence_type=etype, + evidence_ref=ref, + confidence=str(item.get("confidence", "low")).lower(), + verified=bool(etype != "none" and ref), + )) + return result + + @staticmethod + def _parse_detection_gaps(raw_list: Any) -> List[DetectionGap]: + result: List[DetectionGap] = [] + for item in raw_list or []: + if not isinstance(item, dict): + continue + result.append(DetectionGap( + step=str(item.get("step", "")), + control_family=str(item.get("control_family", "none")).upper(), + covered=bool(item.get("covered", False)), + detail=str(item.get("detail", "")), + confidence=str(item.get("confidence", "low")).lower(), + )) + return result + + @staticmethod + def _summarize_turn(turn: DebateTurn) -> str: + if turn.role == "red": + techs = ", ".join(turn.techniques_attempted) or "none" + return f"Red attempted: {techs}. Rationale: {turn.rationale}" + blocked = ", ".join(turn.techniques_blocked) or "none" + return f"Blue blocked: {blocked}. Rationale: {turn.rationale}" + + def _summarize_result( + self, + scenario: Any, + rounds: List[DebateTurn], + final_viability: float, + converged: bool, + convergence_delta: float, + ) -> DebateResult: + blocked_paths: List[str] = [] + residual_gaps: List[DetectionGap] = [] + failed_attempts: List[str] = [] + for turn in rounds: + if turn.role == "red": + failed_attempts.extend(turn.failed_alternatives) + else: + blocked_paths.extend(turn.techniques_blocked) + residual_gaps.extend(g for g in turn.detection_gaps if not g.covered) + + residual_path_viable = final_viability >= self.min_viability_threshold + + return DebateResult( + scenario_id=getattr(scenario, "scenario_id", ""), + objective_name=getattr(scenario, "objective_name", ""), + entry_point=getattr(scenario, "entry_point", ""), + target_asset=getattr(scenario, "target_asset", ""), + rounds=rounds, + blocked_paths=blocked_paths, + residual_detection_gaps=residual_gaps, + red_failed_attempts=failed_attempts, + round_count=sum(1 for t in rounds if t.role == "red"), + convergence_delta=round(convergence_delta, 3), + converged=converged, + final_viability=round(final_viability, 3), + residual_path_viable=residual_path_viable, + debate_factor=1.0, + ) + + # ------------------------------------------------------------------ + # Scoring reinjection (GDAF scenario only — never the STRIDE threat table) + # ------------------------------------------------------------------ + + def _reinject_score(self, scenario: Any, result: DebateResult) -> None: + span = self.debate_factor_max - self.debate_factor_min + base = self.debate_factor_min + span * result.final_viability + factor = base if result.residual_path_viable else min(base, 1.0) + + scenario.path_score_pre_debate = scenario.path_score + scenario.path_score = round(scenario.path_score * factor, 2) + thresholds = GDAFEngine.get_risk_thresholds() + scenario.risk_level = compute_risk_level(scenario.path_score, thresholds) + scenario.debate_factor = round(factor, 2) + result.debate_factor = scenario.debate_factor diff --git a/threat_analysis/core/gdaf_engine.py b/threat_analysis/core/gdaf_engine.py index 10d2b79..7d4d7bf 100644 --- a/threat_analysis/core/gdaf_engine.py +++ b/threat_analysis/core/gdaf_engine.py @@ -55,6 +55,17 @@ _DETECTION_COVERAGE = {"none": 0.0, "low": 0.2, "medium": 0.5, "high": 0.8} +def compute_risk_level(path_score: float, thresholds: Dict[str, float]) -> str: + """Classify a GDAF path_score into CRITICAL/HIGH/MEDIUM/LOW using calibrated thresholds.""" + if path_score >= thresholds.get("CRITICAL", 4.0): + return "CRITICAL" + if path_score >= thresholds.get("HIGH", 2.8): + return "HIGH" + if path_score >= thresholds.get("MEDIUM", 1.8): + return "MEDIUM" + return "LOW" + + @dataclass class AttackHop: asset_name: str @@ -87,6 +98,8 @@ class AttackScenario: detection_coverage: float # 0.0 (no detection controls mapped) unacceptable_risk: bool min_technique_score: float = 0.8 # threshold for OR-branch rendering in .afb + path_score_pre_debate: Optional[float] = None # set by RedBlueDebateEngine; None if never debated + debate_factor: float = 1.0 # multiplier applied to path_score by RedBlueDebateEngine class GDAFEngine: @@ -113,6 +126,17 @@ def _load_scoring_config(cls) -> Dict: cls._scoring_config = {} return cls._scoring_config + @classmethod + def get_risk_thresholds(cls) -> Dict[str, float]: + """Return merged risk_thresholds (scoring_config.yaml overrides over built-in defaults).""" + cfg = cls._load_scoring_config().get("gdaf", {}) + rt = cfg.get("risk_thresholds", {}) + return { + "CRITICAL": float(rt.get("CRITICAL", 4.0)), + "HIGH": float(rt.get("HIGH", 2.8)), + "MEDIUM": float(rt.get("MEDIUM", 1.8)), + } + def __init__( self, threat_model: Any, @@ -684,12 +708,7 @@ def _build_scenario( # Thresholds calibrated for avg_tech_score × hop_weight scoring: # hop_weight ~ 1.0–2.0, avg_tech_score ~ 1.0–2.5 → hop_score ~ 1.0–5.0 # path_score (average) ~ 1.0–5.0 + CIA bonus 0–0.5 - risk_level = ( - "CRITICAL" if path_score >= self._risk_thresholds["CRITICAL"] else - "HIGH" if path_score >= self._risk_thresholds["HIGH"] else - "MEDIUM" if path_score >= self._risk_thresholds["MEDIUM"] else - "LOW" - ) + risk_level = compute_risk_level(path_score, self._risk_thresholds) target_name = path[-1][0] diff --git a/threat_analysis/generation/report_generator.py b/threat_analysis/generation/report_generator.py index 401f92b..c3f5aeb 100644 --- a/threat_analysis/generation/report_generator.py +++ b/threat_analysis/generation/report_generator.py @@ -291,6 +291,7 @@ def __init__(self, severity_calculator, mitre_mapping, self._ranking_weights: Dict[str, float] = {} self._enrich_batch_size: int = 5 self._enrich_max_concurrent: int = 3 + self._debate_config: Dict = {} if ai_config_path and ai_config_path.exists(): with open(ai_config_path, "r", encoding="utf-8") as f: @@ -325,6 +326,7 @@ def __init__(self, severity_calculator, mitre_mapping, self._ranking_weights = {k: float(v) for k, v in rw.items() if isinstance(v, (int, float))} self._enrich_batch_size: int = int(tg.get("batch_size", 5)) self._enrich_max_concurrent: int = int(tg.get("max_concurrent_ai_requests", 3)) + self._debate_config = ai_config.get("debate", {}) or {} async def _run_ciso_triage(self, all_threats: List[Dict]) -> Dict: """Generates a CISO-level risk briefing via the AI provider. @@ -418,6 +420,46 @@ async def _run_ciso_triage(self, all_threats: List[Dict]) -> Dict: ) return result + async def _run_debate(self, threat_model: Any) -> List[Any]: + """Runs the Red/Blue adversarial debate over GDAF attack scenarios. + + Returns an empty list when debate is disabled, AI is unavailable, or no + GDAF scenarios exist. Never raises — mirrors _run_ciso_triage's + degrade-silently contract. + """ + if not self._debate_config.get("enabled", False): + return [] + if not self.ai_provider: + return [] + + scenarios = getattr(threat_model, "gdaf_scenarios", None) or [] + if not scenarios: + return [] + + try: + _client = await self.ai_provider._get_client() + if not _client.ai_online: + return [] + except Exception: + try: + if not await self.ai_provider.check_connection(): + return [] + except Exception: + pass # proceed; generate_debate_turn() will fail safely if offline + + from threat_analysis.core.debate_engine import RedBlueDebateEngine # noqa: PLC0415 + bom_dir = resolve_bom_directory(threat_model) + + engine = RedBlueDebateEngine(self.ai_provider, config=self._debate_config, bom_directory=bom_dir) + try: + results = await engine.run(scenarios) + except Exception as exc: + logging.warning("Red/Blue debate failed (non-fatal): %s", exc) + return [] + + logging.info("Red/Blue debate: produced %d results", len(results)) + return results + @staticmethod def _build_ai_context_from_model(threat_model: "ThreatModel") -> Dict[str, Any]: """Build AI context dict from DSL ## Context keys on the threat model. @@ -672,6 +714,15 @@ def generate_html_report(self, threat_model, grouped_threats: Dict[str, List], threat_model._completeness = completeness threat_model._attack_id_validation = attack_id_validation + # Red/Blue adversarial debate — mutates gdaf_scenarios in place (score/risk_level only). + debate_results = [] + if self._debate_config.get("enabled") and self.ai_provider and getattr(threat_model, "gdaf_scenarios", None): + try: + debate_results = asyncio.run(self._run_debate(threat_model)) + except Exception as exc: + logging.warning("Red/Blue debate failed (non-fatal): %s", exc) + threat_model.debate_results = debate_results + # Build a serialised summary of GDAF scenarios for the HTML template. # Uses getattr for safety — works even if gdaf_scenarios was never populated. gdaf_data = [] @@ -710,6 +761,10 @@ def generate_html_report(self, threat_model, grouped_threats: Dict[str, List], "target_asset": getattr(scenario, 'target_asset', ''), "path": " → ".join(h["node"] for h in hops_summary), "score": round(float(getattr(scenario, 'path_score', 0)), 2), + "pre_debate_score": ( + round(float(getattr(scenario, 'path_score_pre_debate')), 2) + if getattr(scenario, 'path_score_pre_debate', None) is not None else None + ), "risk_level": getattr(scenario, 'risk_level', 'LOW'), "hop_count": len(hops_summary), "hops": hops_summary, @@ -717,6 +772,58 @@ def generate_html_report(self, threat_model, grouped_threats: Dict[str, List], "unacceptable_risk": bool(getattr(scenario, 'unacceptable_risk', False)), }) + # Build a serialised summary of debate results for the HTML template. + debate_data = [] + for result in debate_results: + rounds_data = [] + for turn in result.rounds: + rounds_data.append({ + "role": turn.role, + "round_index": turn.round_index, + "viability_score": turn.viability_score, + "techniques_attempted": turn.techniques_attempted, + "techniques_blocked": turn.techniques_blocked, + "failed_alternatives": turn.failed_alternatives, + "rationale": turn.rationale, + "detection_gaps": [ + { + "step": g.step, "control_family": g.control_family, + "covered": g.covered, "detail": g.detail, "confidence": g.confidence, + } + for g in turn.detection_gaps + ], + "evidence": [ + { + "claim": e.claim, "evidence_type": e.evidence_type, + "evidence_ref": e.evidence_ref, "confidence": e.confidence, + "verified": e.verified, + } + for e in turn.evidence + ], + }) + debate_data.append({ + "scenario_id": result.scenario_id, + "objective_name": result.objective_name, + "entry_point": result.entry_point, + "target_asset": result.target_asset, + "blocked_paths": result.blocked_paths, + "residual_detection_gaps": [ + { + "step": g.step, "control_family": g.control_family, + "covered": g.covered, "detail": g.detail, "confidence": g.confidence, + } + for g in result.residual_detection_gaps + ], + "red_failed_attempts": result.red_failed_attempts, + "round_count": result.round_count, + "convergence_delta": result.convergence_delta, + "converged": result.converged, + "final_viability": result.final_viability, + "residual_path_viable": result.residual_path_viable, + "debate_factor": result.debate_factor, + "rounds": rounds_data, + }) + template = self.env.get_template('report_template.html') html = template.render( title="STRIDE & MITRE ATT&CK Report", @@ -733,6 +840,7 @@ def generate_html_report(self, threat_model, grouped_threats: Dict[str, List], implemented_mitigation_ids=self.implemented_mitigations, attack_chains=attack_chains, gdaf_scenarios=gdaf_data, + debate_results=debate_data, ciso_triage=ciso_triage, completeness=completeness, attack_id_validation=attack_id_validation, diff --git a/threat_analysis/server/ai_service.py b/threat_analysis/server/ai_service.py index 67d6139..96df579 100644 --- a/threat_analysis/server/ai_service.py +++ b/threat_analysis/server/ai_service.py @@ -419,7 +419,7 @@ async def _generate_rag_threats(self, threat_model) -> List[ExtendedThreat]: # R def _model_markdown(tm, label: str = "") -> str: """Render a single ThreatModel as Markdown for RAG context.""" header = label or tm.tm.name - md = f"# Threat Model: {header}\n\n" + md = f"# System Model: {header}\n\n" md += f"## Description\n\n{tm.tm.description}\n\n" md += "## Components\n\n" # Actors and servers may be stored as dicts (with 'object' key) or as objects diff --git a/threat_analysis/server/server.py b/threat_analysis/server/server.py index 9d0c687..19ebb0d 100644 --- a/threat_analysis/server/server.py +++ b/threat_analysis/server/server.py @@ -24,7 +24,7 @@ import time from pathlib import Path from typing import Dict, List, Any, Optional -from flask import Flask, render_template, request, jsonify, send_from_directory, send_file, make_response, Response, stream_with_context, g +from flask import Flask, render_template, request, jsonify, send_from_directory, send_file, make_response, Response, stream_with_context, g, abort from threat_analysis import config import asyncio from threat_analysis.server.events import ai_status_event_queue @@ -217,8 +217,9 @@ def generate_config_files(): initial_markdown_content = "" initial_project_path = None initial_model_file_path: Optional[str] = None +graphical_editor_enabled = False -DEFAULT_EMPTY_MARKDOWN = """# Threat Model: New Model +DEFAULT_EMPTY_MARKDOWN = """# System Model: New Model ## Description A new threat model. Describe your system here. @@ -246,13 +247,23 @@ def generate_config_files(): def get_model_name(markdown_content: str) -> str: - match = re.search(r"^# Threat Model: (.*)$", markdown_content, re.MULTILINE) + match = re.search(r"^# (Threat|System) Model: (.*)$", markdown_content, re.MULTILINE) if match: - return match.group(1).strip() + if match.group(1) == "Threat": + logging.warning( + "Model title uses the deprecated '# Threat Model: ...' tag — use " + "'# System Model: ...' instead (the DSL file describes the system model; " + "the threat model is what SecOpsTM generates from it)." + ) + return match.group(2).strip() return "Untitled Model" -def run_server(model_filepath: Optional[str] = None, project_path: Optional[str] = None): +def run_server( + model_filepath: Optional[str] = None, + project_path: Optional[str] = None, + enable_graphical_editor: bool = False, +): """ This function is the main entry point for the web server. It launches the Flask application on a single port and serves a menu @@ -264,6 +275,8 @@ def run_server(model_filepath: Optional[str] = None, project_path: Optional[str] global initial_markdown_content global initial_project_path global initial_model_file_path + global graphical_editor_enabled + graphical_editor_enabled = enable_graphical_editor effective_model_path = None if project_path and os.path.isdir(project_path): @@ -337,7 +350,7 @@ def get_data_dictionary(): @app.route("/") def index(): """Serves the main menu.""" - return render_template("index.html") + return render_template("index.html", graphical_editor_enabled=graphical_editor_enabled) @app.route("/simple") def simple_mode(): @@ -373,6 +386,8 @@ def graphical_editor(): """ Serves the main web interface. """ + if not graphical_editor_enabled: + abort(404) return render_template( "graphical_editor.html", ai_online=get_threat_model_service().ai_online # Pass AI status to template @@ -473,7 +488,8 @@ async def generate_markdown_from_prompt(): else: logging.warning(f"AI response did not contain a clear markdown block. Raw response preview: {full_response[:200]}") # Fallback: find the start of the threat model and clean up common AI chatter. - model_start_index = full_response.find("# Threat Model:") + model_start_match = re.search(r"# (?:Threat|System) Model:", full_response) + model_start_index = model_start_match.start() if model_start_match else -1 if model_start_index != -1: extracted_markdown = full_response[model_start_index:].strip() end_block_index = extracted_markdown.rfind("```") @@ -542,7 +558,7 @@ def _format_properties(item: dict, props_to_include: list) -> str: def convert_json_to_markdown(data: dict) -> str: """Converts JSON from the graphical editor to Markdown DSL.""" - markdown_lines = ["# Threat Model: Graphical Editor"] + markdown_lines = ["# System Model: Graphical Editor"] boundaries = data.get("boundaries", []) actors = data.get("actors", []) @@ -594,6 +610,8 @@ def graphical_update(): """ Receives JSON graph data, converts it to Markdown, and returns the analysis. """ + if not graphical_editor_enabled: + abort(404) logging.debug("Entering graphical_update function.") json_data = request.json if not json_data: diff --git a/threat_analysis/server/static/js/ThreatModelGenerator.js b/threat_analysis/server/static/js/ThreatModelGenerator.js index 2938aeb..278d933 100644 --- a/threat_analysis/server/static/js/ThreatModelGenerator.js +++ b/threat_analysis/server/static/js/ThreatModelGenerator.js @@ -275,7 +275,7 @@ class ThreatModelGenerator { } convertJsonToMarkdown(data) { - const markdown_lines = ["# Threat Model: Graphical Editor"]; + const markdown_lines = ["# System Model: Graphical Editor"]; const boundaries = data.boundaries || []; const actors = data.actors || []; @@ -360,7 +360,7 @@ class ThreatModelGenerator { } getModelName(markdownContent) { - const match = markdownContent.match(/^# Threat Model: (.*)$/m); + const match = markdownContent.match(/^# (?:Threat|System) Model: (.*)$/m); return match ? match[1].trim() : "Untitled Model"; } } \ No newline at end of file diff --git a/threat_analysis/server/static/js/config.js b/threat_analysis/server/static/js/config.js index 472cd72..f935507 100644 --- a/threat_analysis/server/static/js/config.js +++ b/threat_analysis/server/static/js/config.js @@ -1,5 +1,5 @@ // Threat Model Configuration - Web UI -// Generated on: 2026-05-18 21:30:14 +// Generated on: 2026-07-05 21:58:05 // This file contains configuration for the threat model web interface const ThreatModelConfig = { diff --git a/threat_analysis/server/templates/index.html b/threat_analysis/server/templates/index.html index 9a68a3d..9e85685 100644 --- a/threat_analysis/server/templates/index.html +++ b/threat_analysis/server/templates/index.html @@ -168,11 +168,13 @@

Simple Mode

Load and view a threat model from a Markdown file.

A simple interface to load a threat model file and view the generated diagram and report. + {% if graphical_editor_enabled %}

Graphical Editor

Interactively create and edit a threat model in your browser.

An advanced, interactive editor to create, modify, and analyze threat models directly in your browser. (Experimental)
+ {% endif %}