From afba5b86b8e8c565b78d213d2cbc9e87e6f32de3 Mon Sep 17 00:00:00 2001
From: Vijay Chegu <21251550+cheguv@users.noreply.github.com>
Date: Sun, 29 Mar 2026 20:58:56 +0000
Subject: [PATCH] Add threat-model-analyst skill: STRIDE-A threat modeling for
repositories (#1177)
* Add threat-model-analyst skill: STRIDE-A threat modeling for repositories
Add a comprehensive threat model analysis skill that performs security audits
using STRIDE-A (STRIDE + Abuse) threat modeling, Zero Trust principles, and
defense-in-depth analysis.
Supports two modes:
- Single analysis: full STRIDE-A threat model producing architecture overviews,
DFD diagrams, prioritized findings, and executive assessments
- Incremental analysis: security posture diff between baseline report and current
code, producing standalone reports with embedded comparison
Includes bundled reference assets:
- Orchestrator workflows (full and incremental)
- Analysis principles and verification checklists
- Output format specifications and skeleton templates
- DFD diagram conventions and TMT element taxonomy
* Address PR review comments from Copilot reviewer
- Fix SKILL.md description: use single-quoted scalar, rename mode (2) to
'Incremental analysis' with accurate description
- Replace 'Compare Mode (Deprecated)' sections with 'Comparing Commits or
Reports' redirect (no deprecated language for first release)
- Fix skeleton-findings.md: move Tier 1 table rows under header, add
CONDITIONAL-EMPTY block after END-REPEAT (matching Tier 2/3 structure)
- Fix skeleton-threatmodel.md and skeleton-architecture.md: use 4-backtick
outer fences to avoid nested fence conflicts with inner mermaid fences
- Fix skeleton-incremental-html.md: correct section count from 9 to 8
- Fix output-formats.md: change status 'open' to 'Open' in JSON example,
move stride_category warning outside JSON fence as blockquote
- Fix incremental-orchestrator.md: replace stale compare-output-formats.md
reference with inline color conventions
- Regenerate docs/README.skills.md with updated description
* Address second round of Copilot review comments
- Fix diagram-conventions.md: bidirectional flow notation now uses <-->
matching orchestrator.md and DFD templates
- Fix tmt-element-taxonomy.md: normalize SE.DF.SSH/LDAP/LDAPS to use
SE.DF.TMCore.* prefix consistent with all other data flow IDs
- Fix output-formats.md: correct TMT category example from SQLDatabase
to SQL matching taxonomy, fix component type from 'datastore' to
'data_store' matching canonical enum, remove DaprSidecar from
inbound_from per no-standalone-sidecar rule
- Fix 5 skeleton files: clarify VERBATIM instruction to 'copy the
template content below (excluding the outer code fence)' to prevent
agents from wrapping output in markdown fences
- Genericize product-specific names in examples: replace edgerag with
myapp, BitNetManager with TaskProcessor, AzureLocalMCP with MyApp.Core,
AzureLocalInfra with OnPremInfra, MilvusVectorDB with VectorDB
* Address third round of Copilot review comments
- Fix diagram-conventions.md: second bidirectional two-arrow pattern in
Quick Reference section now uses <-->
- Fix incremental-orchestrator.md: renumber HTML sections 5-9 to 4-8
matching skeleton-incremental-html.md 8-section structure
- Fix output-formats.md: add incremental-comparison.html to File List
as conditional output for incremental mode
- Fix skeleton-inventory.md: add tmt_type, sidecars, and boundary_kind
fields to match output-formats.md JSON schema example
---
docs/README.skills.md | 1 +
skills/threat-model-analyst/SKILL.md | 75 ++
.../references/analysis-principles.md | 421 +++++++
.../references/diagram-conventions.md | 491 ++++++++
.../references/incremental-orchestrator.md | 708 +++++++++++
.../references/orchestrator.md | 593 +++++++++
.../references/output-formats.md | 1062 +++++++++++++++++
.../skeletons/skeleton-architecture.md | 133 +++
.../skeletons/skeleton-assessment.md | 273 +++++
.../references/skeletons/skeleton-dfd.md | 68 ++
.../references/skeletons/skeleton-findings.md | 197 +++
.../skeletons/skeleton-incremental-html.md | 150 +++
.../skeletons/skeleton-inventory.md | 139 +++
.../skeletons/skeleton-stride-analysis.md | 106 ++
.../skeletons/skeleton-summary-dfd.md | 62 +
.../skeletons/skeleton-threatmodel.md | 65 +
.../references/tmt-element-taxonomy.md | 187 +++
.../references/verification-checklist.md | 639 ++++++++++
18 files changed, 5370 insertions(+)
create mode 100644 skills/threat-model-analyst/SKILL.md
create mode 100644 skills/threat-model-analyst/references/analysis-principles.md
create mode 100644 skills/threat-model-analyst/references/diagram-conventions.md
create mode 100644 skills/threat-model-analyst/references/incremental-orchestrator.md
create mode 100644 skills/threat-model-analyst/references/orchestrator.md
create mode 100644 skills/threat-model-analyst/references/output-formats.md
create mode 100644 skills/threat-model-analyst/references/skeletons/skeleton-architecture.md
create mode 100644 skills/threat-model-analyst/references/skeletons/skeleton-assessment.md
create mode 100644 skills/threat-model-analyst/references/skeletons/skeleton-dfd.md
create mode 100644 skills/threat-model-analyst/references/skeletons/skeleton-findings.md
create mode 100644 skills/threat-model-analyst/references/skeletons/skeleton-incremental-html.md
create mode 100644 skills/threat-model-analyst/references/skeletons/skeleton-inventory.md
create mode 100644 skills/threat-model-analyst/references/skeletons/skeleton-stride-analysis.md
create mode 100644 skills/threat-model-analyst/references/skeletons/skeleton-summary-dfd.md
create mode 100644 skills/threat-model-analyst/references/skeletons/skeleton-threatmodel.md
create mode 100644 skills/threat-model-analyst/references/tmt-element-taxonomy.md
create mode 100644 skills/threat-model-analyst/references/verification-checklist.md
diff --git a/docs/README.skills.md b/docs/README.skills.md
index 259ea5d8..ba71dbdd 100644
--- a/docs/README.skills.md
+++ b/docs/README.skills.md
@@ -257,6 +257,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-skills) for guidelines on how to
| [swift-mcp-server-generator](../skills/swift-mcp-server-generator/SKILL.md) | Generate a complete Model Context Protocol server project in Swift using the official MCP Swift SDK package. | None |
| [technology-stack-blueprint-generator](../skills/technology-stack-blueprint-generator/SKILL.md) | Comprehensive technology stack blueprint generator that analyzes codebases to create detailed architectural documentation. Automatically detects technology stacks, programming languages, and implementation patterns across multiple platforms (.NET, Java, JavaScript, React, Python). Generates configurable blueprints with version information, licensing details, usage patterns, coding conventions, and visual diagrams. Provides implementation-ready templates and maintains architectural consistency for guided development. | None |
| [terraform-azurerm-set-diff-analyzer](../skills/terraform-azurerm-set-diff-analyzer/SKILL.md) | Analyze Terraform plan JSON output for AzureRM Provider to distinguish between false-positive diffs (order-only changes in Set-type attributes) and actual resource changes. Use when reviewing terraform plan output for Azure resources like Application Gateway, Load Balancer, Firewall, Front Door, NSG, and other resources with Set-type attributes that cause spurious diffs due to internal ordering changes. | `references/azurerm_set_attributes.json`
`references/azurerm_set_attributes.md`
`scripts/.gitignore`
`scripts/README.md`
`scripts/analyze_plan.py` |
+| [threat-model-analyst](../skills/threat-model-analyst/SKILL.md) | Full STRIDE-A threat model analysis and incremental update skill for repositories and systems. Supports two modes: (1) Single analysis — full STRIDE-A threat model of a repository, producing architecture overviews, DFD diagrams, STRIDE-A analysis, prioritized findings, and executive assessments. (2) Incremental analysis — takes a previous threat model report as baseline, compares the codebase at the latest (or a given commit), and produces an updated report with change tracking (new, resolved, still-present threats), STRIDE heatmap, findings diff, and an embedded HTML comparison. Only activate when the user explicitly requests a threat model analysis, incremental update, or invokes /threat-model-analyst directly. | `references/analysis-principles.md`
`references/diagram-conventions.md`
`references/incremental-orchestrator.md`
`references/orchestrator.md`
`references/output-formats.md`
`references/skeletons`
`references/tmt-element-taxonomy.md`
`references/verification-checklist.md` |
| [tldr-prompt](../skills/tldr-prompt/SKILL.md) | Create tldr summaries for GitHub Copilot files (prompts, agents, instructions, collections), MCP servers, or documentation from URLs and queries. | None |
| [transloadit-media-processing](../skills/transloadit-media-processing/SKILL.md) | Process media files (video, audio, images, documents) using Transloadit. Use when asked to encode video to HLS/MP4, generate thumbnails, resize or watermark images, extract audio, concatenate clips, add subtitles, OCR documents, or run any media processing pipeline. Covers 86+ processing robots for file transformation at scale. | None |
| [typescript-mcp-server-generator](../skills/typescript-mcp-server-generator/SKILL.md) | Generate a complete MCP server project in TypeScript with tools, resources, and proper configuration | None |
diff --git a/skills/threat-model-analyst/SKILL.md b/skills/threat-model-analyst/SKILL.md
new file mode 100644
index 00000000..9b38ea26
--- /dev/null
+++ b/skills/threat-model-analyst/SKILL.md
@@ -0,0 +1,75 @@
+---
+name: threat-model-analyst
+description: 'Full STRIDE-A threat model analysis and incremental update skill for repositories and systems. Supports two modes: (1) Single analysis — full STRIDE-A threat model of a repository, producing architecture overviews, DFD diagrams, STRIDE-A analysis, prioritized findings, and executive assessments. (2) Incremental analysis — takes a previous threat model report as baseline, compares the codebase at the latest (or a given commit), and produces an updated report with change tracking (new, resolved, still-present threats), STRIDE heatmap, findings diff, and an embedded HTML comparison. Only activate when the user explicitly requests a threat model analysis, incremental update, or invokes /threat-model-analyst directly.'
+---
+
+# Threat Model Analyst
+
+You are an expert **Threat Model Analyst**. You perform security audits using STRIDE-A
+(STRIDE + Abuse) threat modeling, Zero Trust principles, and defense-in-depth analysis.
+You flag secrets, insecure boundaries, and architectural risks.
+
+## Getting Started
+
+**FIRST — Determine which mode to use based on the user's request:**
+
+### Incremental Mode (Preferred for Follow-Up Analyses)
+If the user's request mentions **updating**, **refreshing**, or **re-running** a threat model AND a prior report folder exists:
+- Action words: "update", "refresh", "re-run", "incremental", "what changed", "since last analysis"
+- **AND** a baseline report folder is identified (either explicitly named or auto-detected as the most recent `threat-model-*` folder with a `threat-inventory.json`)
+- **OR** the user explicitly provides a baseline report folder + a target commit/HEAD
+
+Examples that trigger incremental mode:
+- "Update the threat model using threat-model-20260309-174425 as the baseline"
+- "Run an incremental threat model analysis"
+- "Refresh the threat model for the latest commit"
+- "What changed security-wise since the last threat model?"
+
+→ Read [incremental-orchestrator.md](./references/incremental-orchestrator.md) and follow the **incremental workflow**.
+ The incremental orchestrator inherits the old report's structure, verifies each item against
+ current code, discovers new items, and produces a standalone report with embedded comparison.
+
+### Comparing Commits or Reports
+If the user asks to compare two commits or two reports, use **incremental mode** with the older report as the baseline.
+→ Read [incremental-orchestrator.md](./references/incremental-orchestrator.md) and follow the **incremental workflow**.
+
+### Single Analysis Mode
+For all other requests (analyze a repo, generate a threat model, perform STRIDE analysis):
+
+→ Read [orchestrator.md](./references/orchestrator.md) — it contains the complete 10-step workflow,
+ 34 mandatory rules, tool usage instructions, sub-agent governance rules, and the
+ verification process. Do not skip this step.
+
+## Reference Files
+
+Load the relevant file when performing each task:
+
+| File | Use When | Content |
+|------|----------|---------|
+| [Orchestrator](./references/orchestrator.md) | **Always — read first** | Complete 10-step workflow, 34 mandatory rules, sub-agent governance, tool usage, verification process |
+| [Incremental Orchestrator](./references/incremental-orchestrator.md) | **Incremental/update analyses** | Complete incremental workflow: load old skeleton, change detection, generate report with status annotations, HTML comparison |
+| [Analysis Principles](./references/analysis-principles.md) | Analyzing code for security issues | Verify-before-flagging rules, security infrastructure inventory, OWASP Top 10:2025, platform defaults, exploitability tiers, severity standards |
+| [Diagram Conventions](./references/diagram-conventions.md) | Creating ANY Mermaid diagram | Color palette, shapes, sidecar co-location rules, pre-render checklist, DFD vs architecture styles, sequence diagram styles |
+| [Output Formats](./references/output-formats.md) | Writing ANY output file | Templates for 0.1-architecture.md, 1-threatmodel.md, 2-stride-analysis.md, 3-findings.md, 0-assessment.md, common mistakes checklist |
+| [Skeletons](./references/skeletons/) | **Before writing EACH output file** | 8 verbatim fill-in skeletons (`skeleton-*.md`) — read the relevant skeleton, copy VERBATIM, fill `[FILL]` placeholders. One skeleton per output file. Loaded on-demand to minimize context usage. |
+| [Verification Checklist](./references/verification-checklist.md) | Final verification pass + inline quick-checks | All quality gates: inline quick-checks (run after each file write), per-file structural, diagram rendering, cross-file consistency, evidence quality, JSON schema — designed for sub-agent delegation |
+| [TMT Element Taxonomy](./references/tmt-element-taxonomy.md) | Identifying DFD elements from code | Complete TMT-compatible element type taxonomy, trust boundary detection, data flow patterns, code analysis checklist |
+
+## When to Activate
+
+**Incremental Mode** (read [incremental-orchestrator.md](./references/incremental-orchestrator.md) for workflow):
+- Update or refresh an existing threat model analysis
+- Generate a new analysis that builds on a prior report's structure
+- Track what threats/findings were fixed, introduced, or remain since a baseline
+- When a prior `threat-model-*` folder exists and the user wants a follow-up analysis
+
+**Single Analysis Mode:**
+- Perform full threat model analysis of a repository or system
+- Generate threat model diagrams (DFD) from code
+- Perform STRIDE-A analysis on components and data flows
+- Validate security control implementations
+- Identify trust boundary violations and architectural risks
+- Write prioritized security findings with CVSS 4.0 / CWE / OWASP mappings
+
+**Comparing commits or reports:**
+- To compare security posture between commits, use incremental mode with the older report as baseline
diff --git a/skills/threat-model-analyst/references/analysis-principles.md b/skills/threat-model-analyst/references/analysis-principles.md
new file mode 100644
index 00000000..be8a2dd3
--- /dev/null
+++ b/skills/threat-model-analyst/references/analysis-principles.md
@@ -0,0 +1,421 @@
+# Analysis Principles — Security Analysis Methodology
+
+This file contains ALL rules for how to analyze code for security threats. It is self-contained — everything needed to perform correct, evidence-based security analysis is here.
+
+---
+
+## ⛔ CRITICAL: Verify Before Flagging
+
+**NEVER flag a security gap without confirming it exists.** Many platforms have secure defaults.
+
+### Three-Step Verification
+
+1. **Check for security infrastructure components** before claiming security is missing:
+ - Certificate authorities (Dapr Sentry, cert-manager, Vault)
+ - Service mesh control planes (Istio, Linkerd, Dapr)
+ - Policy engines (OPA, Kyverno, Gatekeeper)
+ - Secret managers (Vault, Azure Key Vault, AWS Secrets Manager)
+ - Identity providers (MISE, OAuth proxies, OIDC)
+
+2. **Understand platform defaults** — research before assuming:
+ - Dapr: mTLS enabled by default when Sentry is deployed
+ - Kubernetes: RBAC enabled by default since v1.6
+ - Istio: mTLS in PERMISSIVE mode by default, STRICT available
+ - Azure: Many services encrypted at rest by default
+
+3. **Distinguish configuration states**:
+ - **Explicitly disabled**: `enabled: false` → Flag as finding
+ - **Not configured**: No setting present → Check platform default first
+ - **Implicitly enabled**: Default behavior is secure → Document as control, not gap
+
+### Evidence Quality Requirements
+
+For every finding:
+- Show the specific config/code that proves the gap (not just absence of config)
+- For "missing security" claims, prove the default is insecure
+- Cross-reference with platform documentation when uncertain
+
+---
+
+## Security Infrastructure Inventory
+
+Before STRIDE-A analysis, identify ALL security-enabling components present in the codebase:
+
+| Category | Components to Look For | Security They Provide |
+|----------|----------------------|----------------------|
+| Service Mesh | Dapr, Istio, Linkerd, Consul Connect | mTLS, traffic policies, observability |
+| Certificate Management | Sentry, cert-manager, Vault PKI | Automatic cert issuance/rotation |
+| Authentication | MISE, OAuth2-proxy, Dex, Keycloak | Token validation, SSO |
+| Authorization | OPA, Kyverno, Gatekeeper, RBAC | Policy enforcement |
+| Secrets | Vault, External Secrets, CSI drivers | Secret injection, rotation |
+| Network | NetworkPolicy, Calico, Cilium | Microsegmentation |
+
+**If these components exist, their security features are likely active unless explicitly disabled.**
+
+---
+
+## Security Analysis Lenses
+
+Apply these frameworks during analysis:
+
+- **Zero Trust**: Verify explicitly, least privilege, assume breach
+- **Defense in Depth**: Identify missing security layers
+- **Abuse Cases**: Business logic abuse, workflow manipulation, feature misuse
+
+---
+
+## Comprehensive Coverage Requirements
+
+**Do NOT truncate analysis for larger codebases.** All components must receive equal analytical depth.
+
+### Sidecar Security Analysis
+
+⚠️ **Sidecars (Dapr, MISE, Envoy, etc.) are NOT separate components in the DFD** — they are co-located in the same pod as the primary container (see diagram-conventions.md Rule 2). However, sidecar communication MUST still be analyzed for security vulnerabilities.
+
+**How to analyze sidecar threats:**
+- Sidecars with distinct threat surfaces (e.g., MISE auth bypass, Dapr mTLS) get their own `## Component` section in `2-stride-analysis.md` — but are NOT separate DFD nodes (see diagram-conventions.md Rule 2)
+- Use the format: threat title includes the sidecar name, e.g., "Dapr Sidecar Plaintext Communication"
+- Common sidecar threats:
+ - **Information Disclosure (I):** Dapr/MISE sidecar communicating with main container over plaintext HTTP within the pod
+ - **Tampering (T):** Dapr pub/sub messages not signed or encrypted
+ - **Spoofing (S):** MISE token validation bypass if sidecar is compromised
+ - **Elevation of Privilege (E):** Sidecar running with elevated privileges that the main container doesn't need
+- CWE mapping: CWE-319 (Cleartext Transmission), CWE-311 (Missing Encryption), CWE-250 (Unnecessary Privileges)
+- These threats appear in the sidecar's own STRIDE section (if it has a distinct threat surface) or under the primary component's table (if the sidecar is a simple infrastructure proxy)
+- If the sidecar vulnerability warrants a finding, list it under the sidecar component with a note: "Affects [Dapr/MISE] sidecar communication"
+
+1. **Minimum coverage:** Every component in `0.1-architecture.md` MUST have a corresponding section in `2-stride-analysis.md` with actual threat enumeration (not just "no threats found").
+2. **Finding density check:** As a guideline, expect roughly 1 finding per 2-3 significant components. If a repo has 15+ components and you have fewer than 8 findings, re-examine under-analyzed components.
+3. **Use sub-agents for scale:** For repos with 10+ components, delegate component-specific STRIDE analysis to sub-agents to maintain depth. Each sub-agent should analyze 3-5 components.
+4. **OWASP checklist sweep:** After component-level STRIDE, do a cross-cutting pass using the OWASP Top 10:2025 checklist below. This catches systemic issues (missing auth, no audit logging, no rate limiting, unsigned images) that component-level analysis may miss.
+5. **Infrastructure-layer check:** Explicitly check for: container security contexts, network policies, resource limits, image signing, secrets management, backup/DR controls, and monitoring/alerting gaps.
+6. **Exhaustive findings consolidation:** After STRIDE analysis is complete, scan the STRIDE output for ALL identified threats. Every threat MUST map to either:
+ - A finding in `3-findings.md` (consolidated with related threats)
+ - A `🔄 Mitigated by Platform` entry in the Threat Coverage Verification table (for platform-handled threats only)
+
+ **⛔ EVERY `Open` THREAT MUST HAVE A FINDING.** The tool does NOT have authority to accept risks, defer threats, or decide that a threat is "acceptable." That is the engineering team's decision. The tool's job is to identify ALL threats and create findings for them. The Coverage table should show `✅ Covered (FIND-XX)` for every Open threat — NEVER `⚠️ Accepted Risk`.
+
+ If you have 40+ threats in STRIDE but only 10 findings, you are under-consolidating. Check for missed data store auth, operational controls, credential management, and supply chain issues.
+
+ **⛔ "ACCEPTED RISK" IS FORBIDDEN (MANDATORY):**
+ - **NEVER use `⚠️ Accepted Risk` as a Coverage table status.** This label implies the tool has accepted a risk on behalf of the engineering team. It has not. It cannot.
+ - **NEVER use `Accepted` as a STRIDE Status value.** Use `Open`, `Mitigated`, or `Platform` only.
+ - If you are tempted to write "Accepted Risk" → create a finding instead. The finding's remediation section tells the team what to do. The team decides whether to accept, fix, or defer.
+
+ **⛔ NEEDS REVIEW RESTRICTIONS (MANDATORY):**
+ - **Tier 1 threats (prerequisites = `None`) MUST NEVER be classified as "⚠️ Needs Review."** A threat exploitable by an unauthenticated external attacker cannot be deferred — it MUST become a finding.
+ - **If a threat has a mitigation listed in the STRIDE analysis, it SHOULD become a finding.** The mitigation text is the remediation — use it to write the finding. Only defer to "Needs Review" if the mitigation is genuinely not actionable.
+ - **DoS threats with `None` prerequisites are Tier 1 findings**, not hardening opportunities. An unauthenticated attacker flooding an API with no rate limiting is a directly exploitable vulnerability (CWE-770, CWE-400).
+ - **Do NOT batch-classify entire STRIDE categories as Needs Review.** Each threat must be evaluated individually based on its prerequisites and exploitability.
+ - **"⚠️ Needs Review" is reserved for:** Tier 2/3 threats where no technical mitigation is possible (e.g., social engineering), or threats requiring business context the tool doesn't have.
+ - **The automated analysis does NOT have authority to accept risks** — it only identifies them. "Needs Review" signals that a human must decide.
+ - **Maximum Needs Review ratio:** If more than 30% of threats are classified as "Needs Review", re-examine — you are likely under-reporting findings. Typical ratio: 10-20% for a well-analyzed codebase.
+7. **Minimum finding thresholds by repo size:**
+ - Small repo (< 20 source files): 8+ findings expected
+ - Medium repo (20-100 source files): 12+ findings expected
+ - Large repo (100+ source files): 18+ findings expected
+
+ If below threshold, systematically review: auth per component, secrets in code, container security, network segmentation, logging/monitoring, input validation.
+
+8. **Context-aware Platform ratio limits (MANDATORY):**
+
+ After completing the security infrastructure inventory (Step 1), detect the deployment pattern:
+
+ | Pattern | Detection Signal | Platform Limit |
+ |---------|-----------------|----------------|
+ | **K8s Operator** | `controller-runtime`, `kubebuilder`, or `operator-sdk` in go.mod/go.sum; `Reconcile()` functions in source | **≤35%** |
+ | **Standalone Application** | All other repos (web apps, CLI tools, services) | **≤20%** |
+
+ **Why K8s operators have higher Platform ratios:** Operators delegate security to the K8s platform (RBAC for CR access, etcd encryption, API server TLS, webhook cert validation, Azure AD token validation). The operator code CANNOT implement these controls — they are the platform's responsibility. Classifying them as Platform is correct.
+
+ **Action when Platform exceeds limit:**
+ - Review each Platform-classified threat
+ - If the operator CAN take action (e.g., add input validation, add RBAC checks at startup) → reclassify as `Open` with a finding
+ - If the operator genuinely cannot act (e.g., etcd encryption is a cluster admin concern) → Platform is correct
+ - Document the detected pattern and ratio in `0-assessment.md` → Analysis Context & Assumptions
+
+---
+
+## Technology-Specific Security Checklist
+
+**After completing STRIDE analysis**, scan the codebase for each technology below. For every technology found, verify the corresponding security checks are covered in findings or documented as mitigated. This catches specific vulnerabilities that component-level STRIDE often misses.
+
+| Technology Found | MUST Check For | Common Finding |
+|-----------------|---------------|----------------|
+| **Redis** | `requirepass` disabled, no TLS, no ACL | Auth disabled by default → finding |
+| **Milvus** | `authorizationEnabled: false`, no TLS, public gRPC port | Auth disabled by default → finding |
+| **PostgreSQL/SQL DB** | Superuser usage, `ssl=false`, SQL injection, connection string credentials | Input validation + auth |
+| **MongoDB** | Auth disabled, no TLS, `--noauth` flag | Auth disabled by default |
+| **NGINX/Ingress** | Missing TLS, server_info headers, snippet injection, rate limiting | Config hardening |
+| **Docker/Containers** | Running as root, no `USER` directive, host mounts, no seccomp/AppArmor, unsigned images | Container hardening |
+| **ML/AI Models** | Unauthenticated inference endpoint, model poisoning, prompt injection, no input validation | Endpoint auth + input validation |
+| **LLM/Cloud AI** | PII/secrets sent to external LLM, no content filtering, prompt injection, data exfiltration | Data exposure to cloud |
+| **Kubernetes** | No NetworkPolicy, no PodSecurityPolicy/Standards, no resource limits, RBAC gaps | Network segmentation + resource limits |
+| **Helm Charts** | Hardcoded secrets in values.yaml, no image tag pinning, no security contexts | Config + supply chain |
+| **Key Management** | Hardcoded RSA/HMAC keys, weak key generation, no rotation, keys in source | Cryptographic failures |
+| **CI/CD Pipelines** | Secrets in logs, no artifact signing, mutable dependencies, script injection | Supply chain |
+| **REST APIs** | Missing auth, no rate limiting, verbose errors, no input validation | Auth + injection |
+| **gRPC Services** | No TLS, no auth interceptor, reflection enabled in production | Auth + encryption |
+| **Message Queues** | No auth on pub/sub, no encryption, no message signing | Auth + integrity |
+| **NFS/File Shares** | Path traversal, no access control, world-readable mounts | Access control |
+| **Audit/Logging** | No security event logging, log injection, no tamper protection | Monitoring gaps |
+
+**Process:** After writing 3-findings.md, scan this table for technologies present in the repo. For each technology, evaluate its common technology-specific threat patterns based on how that technology is actually used, and ensure any relevant risks are accounted for in the assessment. Add a finding only if an actual threat or meaningful mitigation gap is identified.
+
+---
+
+## OWASP Top 10:2025 Checklist
+
+Check for these vulnerability categories during analysis:
+
+| ID | Category | Check For |
+|----|----------|----------|
+| A01 | Broken Access Control | Missing authZ, privilege escalation, IDOR, CORS misconfig |
+| A02 | Security Misconfiguration | Default creds, verbose errors, unnecessary features, missing hardening |
+| A03 | Software Supply Chain Failures | Vulnerable dependencies, malicious packages, compromised CI/CD |
+| A04 | Cryptographic Failures | Weak algorithms, exposed secrets, improper key management, plaintext data |
+| A05 | Injection | SQL, NoSQL, OS command, LDAP, XSS, template injection |
+| A06 | Insecure Design | Missing security controls at architecture level, threat modeling gaps |
+| A07 | Authentication Failures | Broken auth, weak sessions, credential stuffing, missing MFA |
+| A08 | Software/Data Integrity Failures | Insecure deserialization, unsigned updates, CI/CD tampering |
+| A09 | Security Logging & Alerting Failures | Missing audit logs, no alerting, log injection, insufficient monitoring |
+| A10 | Mishandling of Exceptional Conditions | Poor error handling, race conditions, resource exhaustion |
+
+Reference: https://owasp.org/Top10/2025/
+
+---
+
+## Platform Security Defaults Reference
+
+Before flagging missing security, check these common secure-by-default behaviors:
+
+| Platform | Feature | Default Behavior | How to Verify |
+|----------|---------|------------------|---------------|
+| **Dapr** | mTLS | Enabled when Sentry deployed | Check for `dapr_sentry` or `sentry` component |
+| **Dapr** | Access Control | Deny if policies defined | Look for `accessControl` in Configuration |
+| **Kubernetes** | RBAC | Enabled since v1.6 | Check `--authorization-mode` includes RBAC |
+| **Kubernetes** | Secrets | Base64 encoded (not encrypted) | Check for encryption provider config |
+| **Istio** | mTLS | PERMISSIVE by default | Check PeerAuthentication resources |
+| **Azure Storage** | Encryption at rest | Enabled by default | Always encrypted, check key management |
+| **Azure SQL** | TDE | Enabled by default | Transparent data encryption on |
+| **PostgreSQL** | SSL | Often disabled by default | Check `ssl` parameter |
+| **Redis** | Auth | Disabled by default | Check `requirepass` configuration |
+| **Milvus** | Auth | Disabled by default | Check `authorizationEnabled` |
+| **NGINX Ingress** | TLS | Not enabled by default | Check for TLS secret in Ingress |
+| **Docker** | User | Root by default | Check `USER` in Dockerfile |
+
+**Key insight**: Service meshes (Dapr, Istio, Linkerd) typically enable mTLS automatically. Databases (Redis, Milvus, MongoDB) typically have auth disabled by default.
+
+---
+
+## Exploitability Tiers
+
+Threats are classified into three exploitability tiers based on prerequisites:
+
+| Tier | Label | Prerequisites | Assignment Rule |
+|------|-------|---------------|----------------|
+| **Tier 1** | Direct Exposure | `None` | Exploitable by unauthenticated external attacker with NO prior access. |
+| **Tier 2** | Conditional Risk | Single prerequisite | Requires exactly ONE form of access: `Authenticated User`, `Privileged User`, `Internal Network`, or single `{Boundary} Access`. |
+| **Tier 3** | Defense-in-Depth | Multiple prerequisites or infrastructure access | Requires `Host/OS Access`, `Admin Credentials`, `{Component} Compromise`, `Physical Access`, or multiple prerequisites with `+`. |
+
+### Tier Assignment Rules
+
+**⛔ CANONICAL PREREQUISITE → TIER MAPPING (deterministic, no exceptions):**
+
+Prerequisites MUST use only these values (closed enum). The tier follows mechanically:
+
+| Prerequisite | Tier | Rationale |
+|-------------|------|----------|
+| `None` | **Tier 1** | Unauthenticated external attacker, no prior access |
+| `Authenticated User` | **Tier 2** | Requires valid credentials |
+| `Privileged User` | **Tier 2** | Requires admin/operator role |
+| `Internal Network` | **Tier 2** | Requires position on internal network |
+| `Local Process Access` | **Tier 2** | Requires code execution on same host (localhost listener, IPC) |
+| `Host/OS Access` | **Tier 3** | Requires filesystem, console, or debug access to the host |
+| `Admin Credentials` | **Tier 3** | Requires admin credentials + host access |
+| `Physical Access` | **Tier 3** | Requires physical presence (USB, serial) |
+| `{Component} Compromise` | **Tier 3** | Requires prior compromise of another component |
+| Any `A + B` combination | **Tier 3** | Multiple prerequisites = always Tier 3 |
+
+**⛔ FORBIDDEN prerequisite values:** `Application Access`, `Host Access` (ambiguous — use `Local Process Access` or `Host/OS Access`).
+
+**Deployment context overrides:** If Deployment Classification is `LOCALHOST_DESKTOP` or `LOCALHOST_SERVICE`, the prerequisite `None` is FORBIDDEN for all components — use `Local Process Access` or `Host/OS Access` instead. The tier then follows from the corrected prerequisite.
+
+### ⛔ Prerequisite Determination (MANDATORY — Evidence-Based, Not Judgment-Based)
+
+**Prerequisites MUST be determined from deployment configuration evidence, not from general knowledge or assumptions.** Two independent analysis runs on the same code MUST assign the same prerequisites because they are objective facts about the deployment.
+
+**Generic Decision Procedure (applies to ALL environments):**
+
+1. **Network Exposure Check — Is the component reachable from outside?**
+ - Look for evidence of external exposure in the codebase:
+ - API gateway / reverse proxy routes pointing to the component
+ - Firewall rules or security group configurations
+ - Load balancer configurations
+ - DNS records or public endpoint definitions
+ - If ANY external route exists → prerequisites = `None` for network-based threats
+ - If NO external route exists AND the component is on an internal-only network → prerequisites = `Internal Network`
+
+2. **Authentication Check — Does the endpoint require credentials?**
+ - Look for authentication middleware, decorators, or filters in the component's code:
+ - `@require_auth`, `[Authorize]`, `@login_required`, auth middleware in Express/FastAPI
+ - API key validation in request handlers
+ - OAuth/OIDC token validation
+ - mTLS certificate requirements
+ - If auth is ENFORCED on all endpoints → prerequisite = `Authenticated User`
+ - If auth is OPTIONAL or DISABLED by config flag → prerequisite = `None` (disabled auth = no barrier)
+ - If auth exists but has bypass routes (e.g., `/health`, `/metrics` without auth) → those specific routes have prerequisite = `None`
+
+3. **Authorization Check — What level of access is required?**
+ - If no RBAC/role check beyond authentication → prerequisite stays `Authenticated User`
+ - If admin/operator role required → prerequisite = `Privileged User`
+ - If specific permissions required → prerequisite names the permission (e.g., `ClusterAdmin Role`)
+
+4. **Physical/Local Access Check:**
+ - If the component only listens on `localhost`/`127.0.0.1` → prerequisite = `Local Process Access` (T2)
+ - If access requires console/SSH/filesystem → prerequisite = `Host/OS Access` (T3)
+ - If access requires physical presence (USB, serial port) → prerequisite = `Physical Access` (T3)
+ - If component has no listener (console app, library, outbound-only) → prerequisite = `Host/OS Access` (T3)
+
+5. **Default Rule:** If you cannot determine exposure from config → look up the component's `Min Prerequisite` in the Component Exposure Table. If the table is not yet filled, assume `Local Process Access` (T2) as a safe default for unknown components. **NEVER assume `None` without positive evidence of external reachability.** **NEVER assume `Internal Network` without evidence of network restriction.**
+
+**Platform-Specific Evidence Sources:**
+
+| Platform | Where to check exposure | Internal indicator | External indicator |
+|----------|------------------------|--------------------|--------------------|
+| **Kubernetes** | Service type, Ingress rules, values.yaml | `ClusterIP` service, no Ingress | `LoadBalancer`/`NodePort`, Ingress path exists |
+| **Docker Compose** | `ports:` mapping, network config | No `ports:` mapping, internal network only | `ports: "8080:8080"` maps to host |
+| **Azure App Service** | App settings, access restrictions | VNet integration, private endpoint | Public URL, no IP restrictions |
+| **VM / Bare Metal** | Firewall rules, NSG, iptables | Port blocked in firewall/NSG | Port open, public IP bound |
+| **Serverless (Functions)** | Function auth level, API Management | `authLevel: function/admin` | `authLevel: anonymous` |
+| **.NET / Java / Node** | Startup config, middleware pipeline | `app.UseAuthentication()` enforced | No auth middleware, or auth disabled |
+| **Python (FastAPI/Flask)** | Middleware, dependency injection | `Depends(get_current_user)` on routes | No auth dependency, open routes |
+
+**⛔ NEVER assign prerequisites based on "what seems reasonable" or architecture assumptions.** Check the actual deployment config. The same component MUST get the same prerequisite across runs because the config doesn't change between runs.
+
+**Common violations:**
+- Assigning `Internal Network` to a component that has an ingress route → hides real external exposure
+- Assuming databases are "internal only" without checking if they have a public endpoint or ingress route
+- Assuming ML model servers are "internal" when they may be exposed for direct inference requests
+
+### CVSS-to-Tier Consistency Check (MANDATORY)
+
+**After assigning CVSS vectors AND tiers, cross-check for contradictions:**
+
+| CVSS Metric | Value | Tier Implication |
+|-------------|-------|------------------|
+| `AV:L` (Attack Vector: Local) | Requires local access | **Cannot be Tier 1** — must be T2 or T3 |
+| `AV:A` (Attack Vector: Adjacent) | Requires adjacent network | **Cannot be Tier 1** — must be T2 or T3 |
+| `AV:P` (Attack Vector: Physical) | Requires physical access | **Must be Tier 3** |
+| `PR:H` (Privileges Required: High) | Requires admin/privileged access | **Cannot be Tier 1** — must be T2 or T3 |
+| `PR:L` (Privileges Required: Low) | Requires authenticated user | **Cannot be Tier 1** — must be T2 |
+| `PR:N` + `AV:N` | No privileges, network accessible | Tier 1 candidate (confirm no deployment override) |
+
+⚠️ **If a finding has `AV:L` and `Tier 1`, this is ALWAYS an error.** Fix by either:
+- Changing the tier to T2/T3 (correct approach for localhost-only services), OR
+- Changing the CVSS AV to `AV:N` if the service is actually network-accessible (rare)
+
+⚠️ **If a finding has `PR:H` and `Tier 1`, this is ALWAYS an error.** Admin-required findings are T2 minimum.
+
+### Deployment Context Affects Tier Classification
+
+**CRITICAL: This section OVERRIDES the default tier rules above when specific deployment conditions apply.**
+
+Before assigning tiers, determine the system's deployment model from code, docs, and architecture. Record the **Deployment Classification** and **Component Exposure Table** in `0.1-architecture.md` (see `skeleton-architecture.md`).
+
+**Deployment Classifications and their tier implications:**
+
+| Classification | Description | T1 Allowed? | Min Prerequisite |
+|----------------|-------------|-------------|------------------|
+| `LOCALHOST_DESKTOP` | Console/GUI app, no network listeners (or localhost-only), single-user workstation | ❌ **NO** — all findings T2+ | `Host/OS Access` (T3) or `Local Process Access` (T2) |
+| `LOCALHOST_SERVICE` | Daemon/service binding to 127.0.0.1 only | ❌ **NO** — all findings T2+ | `Local Process Access` (T2) |
+| `AIRGAPPED` | No internet connectivity | ❌ for network-originated attacks | `Internal Network` |
+| `K8S_SERVICE` | Kubernetes Deployment with ClusterIP/LoadBalancer | ✅ YES | Depends on Service type |
+| `NETWORK_SERVICE` | Public API, cloud endpoint, internet-facing | ✅ YES | `None` (if no auth) |
+
+**The Component Exposure Table in `0.1-architecture.md` sets the prerequisite floor per component.** No threat or finding may have a lower prerequisite than the table permits. This table is filled in Step 1 and is binding on all subsequent analysis steps.
+
+**Legacy override table (still applies as fallback):**
+
+| Deployment Indicator | Tier Override Rule |
+|---------------------|-------------------|
+| Binds to `localhost`/`127.0.0.1` only | Cannot be T1 — requires local access (T2 minimum) |
+| Air-gapped / no internet | Downgrade network-based attacks by one tier |
+| Single-admin workstation tool | Cannot be T1 unless exploitable by a non-admin local user |
+| Docker/container on single machine | Docker socket access = T2 (local admin required) |
+| Named pipe / Unix socket | Cannot be T1 — requires local process access |
+
+**How to apply:**
+1. In Step 1 (context gathering), identify deployment model and record in 0.1-architecture.md
+2. In Step 6/7 (finding verification), check each T1 candidate against the table above
+3. If ANY override applies, downgrade to T2 (or T3 if multiple)
+4. Document the override rationale in the finding’s Description
+
+**Example:** Kusto container on air-gapped workstation, listening on port 80 without auth:
+- Default classification: T1 (unauthenticated, port 80)
+- Override: localhost-only + single-admin → **T2** (attacker needs local access to an admin workstation)
+
+**Do NOT override** for:
+- Kubernetes services (any pod can reach them → lateral movement is realistic → keep T1)
+- Network-exposed APIs (any network user can reach them → keep T1)
+- Cloud endpoints (public internet → keep T1)
+- **Network-exposed APIs**: An unauthenticated API on a listening port IS Tier 1.
+
+The prerequisite for Tier 1 is `None` — meaning an **unauthenticated external attacker** with no prior access. If exploiting a vulnerability requires local admin access, OS-level access, or physical presence, it cannot be Tier 1.
+
+---
+
+## Finding Classification
+
+Before documenting each finding, verify:
+
+- [ ] **Positive evidence exists**: Can you show config/code that proves the vulnerability?
+- [ ] **Not a secure default**: Have you checked if the platform enables security by default?
+- [ ] **Security infrastructure checked**: Did you look for Sentry/cert-manager/Vault/etc.?
+- [ ] **Explicit vs implicit**: Is security explicitly disabled, or just not explicitly enabled?
+- [ ] **Platform documentation consulted**: When uncertain, verify against official docs
+
+**Classification outcomes:**
+- **Confirmed**: Positive evidence of vulnerability → Document as finding in `3-findings.md`
+- **Needs Verification**: Unable to confirm but potential risk → Add to "Needs Verification" in `0-assessment.md`
+- **Not a Finding**: Confirmed secure by default or explicitly enabled → Do not document
+
+---
+
+## Severity Standards
+
+### SDL Bugbar Severity
+Classify each finding per: https://www.microsoft.com/en-us/msrc/sdlbugbar
+
+### CVSS 4.0 Score
+Use CVSS v4.0 Base score (0.0-10.0) with vector string.
+Reference: https://www.first.org/cvss/v4.0/specification-document
+
+### CWE
+Assign Common Weakness Enumeration ID and name.
+Reference: https://cwe.mitre.org/
+
+### OWASP
+Map to OWASP Top 10:2025 category if applicable (A01-A10).
+**ALWAYS use `:2025` suffix** (e.g., `A01:2025`), never `:2021`.
+Reference: https://owasp.org/Top10/2025/
+
+### Remediation Effort
+- **Low**: Configuration change, flag toggle, or single-file fix
+- **Medium**: Multi-file code change, new validation logic, or dependency update
+- **High**: Architecture change, new component, or cross-team coordination
+
+### STRIDE Scope Rule
+- **External services** (AzureOpenAI, AzureAD, Redis, PostgreSQL) **DO get** STRIDE sections — they are attack surfaces from your system's perspective
+- **External actors** (Operator, EndUser) **do NOT get** STRIDE sections — they are threat sources, not targets
+- If you have 20 elements and 2 are external actors, you write 18 STRIDE sections
+
+**⚠️ DO NOT include time estimates.** Never add "(hours)", "(days)", "(weeks)", "~1 hour", "~2 hours", or any duration/effort-to-fix estimates anywhere in the output. The effort level (Low/Medium/High) is sufficient.
+
+### Mitigation Type (OWASP-aligned)
+- **Redesign**: Eliminate the threat by changing architecture (OWASP: Avoid)
+- **Standard Mitigation**: Apply well-known, proven security controls (OWASP: Mitigate)
+- **Custom Mitigation**: Implement a bespoke code fix specific to this system (OWASP: Mitigate)
+- **Existing Control**: Team already built a control that addresses this threat — document it (OWASP: Fix)
+- **Accept Risk**: Acknowledge and document the residual risk (requires justification) (OWASP: Accept)
+- **Transfer Risk**: Shift responsibility to user/operator/third-party (e.g., configuration choice, SLA) (OWASP: Transfer)
diff --git a/skills/threat-model-analyst/references/diagram-conventions.md b/skills/threat-model-analyst/references/diagram-conventions.md
new file mode 100644
index 00000000..c02565b0
--- /dev/null
+++ b/skills/threat-model-analyst/references/diagram-conventions.md
@@ -0,0 +1,491 @@
+# Diagram Conventions — Mermaid Diagrams for Threat Models & Architecture
+
+This file contains ALL rules for creating Mermaid diagrams in threat model reports. It is self-contained — everything needed to produce correct diagrams is here.
+
+---
+
+## ⛔ CRITICAL RULES — READ BEFORE DRAWING ANY DIAGRAM
+
+These rules are the most frequently violated. Read them first, and re-check after every diagram.
+
+### Rule 1: Kubernetes Sidecar Co-location (MANDATORY)
+
+When the target system runs on Kubernetes, **containers that share a Pod must be represented together** — never as independent standalone components.
+
+**DO THIS — annotate the primary container's label:**
+```
+InferencingFlow(("Inferencing Flow
+ MISE, Dapr")):::process
+IngestionFlow(("Ingestion Flow
+ MISE, Dapr")):::process
+VectorDbApi(("VectorDB API
+ Dapr")):::process
+```
+
+**DO NOT DO THIS — never create standalone sidecar nodes:**
+```
+❌ MISE(("MISE Sidecar")):::process
+❌ DaprSidecar(("Dapr Sidecar")):::process
+❌ InferencingFlow -->|"localhost"| MISE
+```
+
+**Why:** Sidecars (Dapr, MISE/auth proxy, Envoy, Istio proxy, log collectors) share the Pod's network namespace, lifecycle, and security context with their primary container. They are NOT independent services.
+
+**This rule applies to ALL diagram types:** architecture, threat model, summary.
+
+### Rule 2: No Intra-Pod Flows (MANDATORY)
+
+**DO NOT draw data flows between a primary container and its sidecars.** These are implicit from the co-location annotation.
+
+```
+❌ InferencingFlow -->|"localhost:3500"| DaprSidecar
+❌ InferencingFlow -->|"localhost:8080"| MISE
+```
+
+Intra-pod communication happens on localhost — it has no security boundary and should not appear in the diagram.
+
+### Rule 3: Cross-Boundary Sidecar Flows Originate from Host Container
+
+When a sidecar makes a call that crosses a trust boundary (e.g., MISE → Azure AD, Dapr → Redis), draw the arrow **from the host container node** — never from a standalone sidecar node.
+
+```
+✅ InferencingFlow -->|"HTTPS (MISE auth)"| AzureAD
+✅ IngestionAPI -->|"HTTPS (MISE auth)"| AzureAD
+✅ InferencingFlow -->|"TCP (Dapr)"| Redis
+
+❌ MISESidecar -->|"HTTPS"| AzureAD
+❌ DaprSidecar -->|"TCP"| Redis
+```
+
+If multiple pods have the same sidecar calling the same external target, draw one arrow per host container. Multiple arrows to the same target is correct.
+
+### Rule 4: Element Table — No Separate Sidecar Rows
+
+Do NOT add separate Element Table rows for sidecars. Describe them in the host container's description column:
+
+```
+✅ | Inferencing Flow | Process | API service + MISE auth proxy + Dapr sidecar | Backend Services |
+❌ | MISE Sidecar | Process | Auth proxy for Inferencing Flow | Backend Services |
+```
+
+If a sidecar class has its own threat surface (e.g., MISE auth bypass), it gets a `## Component` section in STRIDE analysis — but it is still NOT a separate diagram node.
+
+---
+
+## Pre-Render Checklist (VERIFY BEFORE FINALIZING)
+
+After drawing ANY diagram, verify:
+
+- [ ] **Every K8s service node annotated with sidecars?** — Each pod's process node includes `
+ SidecarName` for all co-located containers
+- [ ] **Zero standalone sidecar nodes?** — Search diagram for any node named `MISE`, `Dapr`, `Envoy`, `Istio`, `Sidecar` — these must NOT exist as separate nodes
+- [ ] **Zero intra-pod localhost flows?** — No arrows between a container and its sidecars on localhost
+- [ ] **Cross-boundary sidecar flows from host?** — All arrows to external targets (Azure AD, Redis, etc.) originate from the host container node
+- [ ] **Background forced to white?** — `%%{init}%%` block includes `'background': '#ffffff'`
+- [ ] **All classDef include `color:#000000`?** — Black text on every element
+- [ ] **`linkStyle default` present?** — `stroke:#666666,stroke-width:2px`
+- [ ] **All labels quoted?** — `["Name"]`, `(("Name"))`, `-->|"Label"|`
+- [ ] **Subgraph/end pairs matched?** — Every `subgraph` has a closing `end`
+- [ ] **Trust boundary styles applied?** — `stroke:#e31a1c,stroke-width:3px,stroke-dasharray: 5 5`
+
+---
+
+## Color Palette
+
+> **⛔ CRITICAL: Use ONLY these exact hex codes. Do NOT invent colors, use Chakra UI colors (#4299E1, #48BB78, #E53E3E), Tailwind colors, or any other palette. The colors below are from ColorBrewer qualitative palettes for colorblind accessibility. COPY the classDef lines VERBATIM from this file.**
+
+These colors are shared across ALL Mermaid diagrams. Colors are from ColorBrewer qualitative palettes — designed for colorblind accessibility.
+
+| Color Role | Fill | Stroke | Used For |
+|------------|------|--------|----------|
+| Blue | `#6baed6` | `#2171b5` | Services/Processes |
+| Amber | `#fdae61` | `#d94701` | External Interactors |
+| Green | `#74c476` | `#238b45` | Data Stores |
+| Red | n/a | `#e31a1c` | Trust boundaries (threat model only) |
+| Dark gray | n/a | `#666666` | Arrows/links |
+| Text | all: `color:#000000` | | Black text on every element |
+
+### Design Rationale
+
+| Element | Fill | Stroke | Text | Why |
+|---------|------|--------|------|-----|
+| Process | `#6baed6` | `#2171b5` | `#000000` | Medium blue — visible on both themes |
+| External Interactor | `#fdae61` | `#d94701` | `#000000` | Warm amber — distinct from blue/green |
+| Data Store | `#74c476` | `#238b45` | `#000000` | Medium green — natural for storage |
+| Trust Boundary | none | `#e31a1c` | n/a | Red dashed — 3px for visibility |
+| Arrows/Links | n/a | `#666666` | n/a | Dark gray on white background |
+| Background | `#ffffff` | n/a | n/a | Forced white for dark theme safety |
+
+---
+
+## Forced White Background (REQUIRED)
+
+Every Mermaid diagram — flowchart and sequence — MUST include an `%%{init}%%` block that forces a white background. This ensures diagrams render correctly in dark themes.
+
+> **⛔ CRITICAL: Do NOT add `primaryColor`, `secondaryColor`, `tertiaryColor`, or ANY custom color keys to themeVariables. The init block controls ONLY the background and line color. ALL element colors come from classDef lines — never from themeVariables. If you add color overrides to themeVariables, they will BREAK the classDef palette.**
+
+### Flowchart Init Block
+
+Add as the **first line** of every `.mmd` file or ` ```mermaid ` flowchart:
+
+```
+%%{init: {'theme': 'base', 'themeVariables': { 'background': '#ffffff', 'primaryColor': '#ffffff', 'lineColor': '#666666' }}}%%
+```
+
+**THE ABOVE IS THE ONLY ALLOWED INIT BLOCK FOR FLOWCHARTS.** Do not modify it. Do not add keys. Copy it verbatim.
+
+### Arrow / Link Default Styling
+
+Add after classDef lines:
+
+```
+linkStyle default stroke:#666666,stroke-width:2px
+```
+
+### Sequence Diagram Init Block
+
+Sequence diagrams cannot use `classDef`. Use this init block:
+
+```
+%%{init: {'theme': 'base', 'themeVariables': {
+ 'background': '#ffffff',
+ 'actorBkg': '#6baed6', 'actorBorder': '#2171b5', 'actorTextColor': '#000000',
+ 'signalColor': '#666666', 'signalTextColor': '#666666',
+ 'noteBkgColor': '#fdae61', 'noteBorderColor': '#d94701', 'noteTextColor': '#000000',
+ 'activationBkgColor': '#ddeeff', 'activationBorderColor': '#2171b5',
+ 'sequenceNumberColor': '#767676',
+ 'labelBoxBkgColor': '#f0f0f0', 'labelBoxBorderColor': '#666666', 'labelTextColor': '#000000',
+ 'loopTextColor': '#000000'
+}}}%%
+```
+
+---
+
+## Diagram Type: Threat Model (DFD)
+
+Used in: `1-threatmodel.md`, `1.1-threatmodel.mmd`, `1.2-threatmodel-summary.mmd`
+
+### `.mmd` File Format — CRITICAL
+
+The `.mmd` file contains **raw Mermaid source only** — no markdown, no code fences. The file must start on line 1 with:
+```
+%%{init: {'theme': 'base', 'themeVariables': { 'background': '#ffffff', 'primaryColor': '#ffffff', 'lineColor': '#666666' }}}%%
+```
+Followed by `flowchart LR` on line 2. NEVER use `flowchart TB`.
+
+**WRONG**: File starts with ` ```plaintext ` or ` ```mermaid ` — these are code fences and corrupt the `.mmd` file.
+
+### ClassDef & Shapes
+
+```
+classDef process fill:#6baed6,stroke:#2171b5,stroke-width:2px,color:#000000
+classDef external fill:#fdae61,stroke:#d94701,stroke-width:2px,color:#000000
+classDef datastore fill:#74c476,stroke:#238b45,stroke-width:2px,color:#000000
+```
+
+| Element Type | Shape Syntax | Example |
+|-------------|-------------|---------|
+| Process | `(("Name"))` circle | `WebApi(("Web API")):::process` |
+| External Interactor | `["Name"]` rectangle | `User["User/Browser"]:::external` |
+| Data Store | `[("Name")]` cylinder | `Database[("PostgreSQL")]:::datastore` |
+
+### Trust Boundary Styling
+
+```
+subgraph BoundaryId["Display Name"]
+ %% elements inside
+end
+style BoundaryId fill:none,stroke:#e31a1c,stroke-width:3px,stroke-dasharray: 5 5
+```
+
+### Flow Labels
+
+```
+Unidirectional: A -->|"Label"| B
+Bidirectional: A <-->|"Label"| B
+```
+
+### Data Flow IDs
+
+- Detailed flows: `DF01`, `DF02`, `DF03`...
+- Summary flows: `SDF01`, `SDF02`, `SDF03`...
+
+### Complete DFD Template
+
+```mermaid
+%%{init: {'theme': 'base', 'themeVariables': { 'background': '#ffffff', 'primaryColor': '#ffffff', 'lineColor': '#666666' }}}%%
+flowchart LR
+ classDef process fill:#6baed6,stroke:#2171b5,stroke-width:2px,color:#000000
+ classDef external fill:#fdae61,stroke:#d94701,stroke-width:2px,color:#000000
+ classDef datastore fill:#74c476,stroke:#238b45,stroke-width:2px,color:#000000
+ linkStyle default stroke:#666666,stroke-width:2px
+
+ User["User/Browser"]:::external
+
+ subgraph Internal["Internal Network"]
+ WebApi(("Web API")):::process
+ Database[("PostgreSQL")]:::datastore
+ end
+
+ User <-->|"HTTPS"| WebApi
+ WebApi <-->|"SQL/TLS"| Database
+
+ style Internal fill:none,stroke:#e31a1c,stroke-width:3px,stroke-dasharray: 5 5
+```
+
+### Kubernetes DFD Template (With Sidecars)
+
+```mermaid
+%%{init: {'theme': 'base', 'themeVariables': { 'background': '#ffffff', 'primaryColor': '#ffffff', 'lineColor': '#666666' }}}%%
+flowchart LR
+ classDef process fill:#6baed6,stroke:#2171b5,stroke-width:2px,color:#000000
+ classDef external fill:#fdae61,stroke:#d94701,stroke-width:2px,color:#000000
+ classDef datastore fill:#74c476,stroke:#238b45,stroke-width:2px,color:#000000
+ linkStyle default stroke:#666666,stroke-width:2px
+
+ User["User/Browser"]:::external
+ IdP["Identity Provider"]:::external
+
+ subgraph K8s["Kubernetes Cluster"]
+ subgraph Backend["Backend Services"]
+ ApiService(("API Service
+ AuthProxy, Dapr")):::process
+ Worker(("Worker
+ Dapr")):::process
+ end
+ Redis[("Redis")]:::datastore
+ Database[("PostgreSQL")]:::datastore
+ end
+
+ User -->|"HTTPS"| ApiService
+ ApiService -->|"HTTPS"| User
+ ApiService -->|"HTTPS"| IdP
+ ApiService -->|"SQL/TLS"| Database
+ ApiService -->|"Dapr HTTP"| Worker
+ ApiService -->|"TCP"| Redis
+ Worker -->|"SQL/TLS"| Database
+
+ style K8s fill:none,stroke:#e31a1c,stroke-width:3px,stroke-dasharray: 5 5
+ style Backend fill:none,stroke:#e31a1c,stroke-width:3px,stroke-dasharray: 5 5
+```
+
+**Key points:**
+- AuthProxy and Dapr are annotated on the host node (`+ AuthProxy, Dapr`), not as separate nodes
+- `ApiService -->|"HTTPS"| IdP` = auth proxy's cross-boundary call, drawn from host container
+- `ApiService -->|"TCP"| Redis` = Dapr's cross-boundary call, drawn from host container
+- No intra-pod flows drawn
+
+---
+
+## Diagram Type: Architecture
+
+Used in: `0.1-architecture.md` only
+
+### ClassDef & Shapes
+
+```
+classDef service fill:#6baed6,stroke:#2171b5,stroke-width:2px,color:#000000
+classDef external fill:#fdae61,stroke:#d94701,stroke-width:2px,color:#000000
+classDef datastore fill:#74c476,stroke:#238b45,stroke-width:2px,color:#000000
+```
+
+| Element Type | Shape Syntax | Notes |
+|-------------|-------------|-------|
+| Services/Processes | `["Name"]` or `(["Name"])` | Rounded rectangles or stadium |
+| External Actors | `(["Name"])` with `external` class | Amber distinguishes them |
+| Data Stores | `[("Name")]` cylinder | Same as DFD |
+| **DO NOT** use circles `(("Name"))` | | Reserved for DFD threat model diagrams |
+
+### Layer Grouping Styling (NOT trust boundaries)
+
+```
+style LayerId fill:#f0f4ff,stroke:#2171b5,stroke-width:2px,stroke-dasharray: 5 5
+```
+
+Layer colors:
+- Backend: `fill:#f0f4ff,stroke:#2171b5` (light blue)
+- Data: `fill:#f0fff0,stroke:#238b45` (light green)
+- External: `fill:#fff8f0,stroke:#d94701` (light amber)
+- Infrastructure: `fill:#f5f5f5,stroke:#666666` (light gray)
+
+### Flow Conventions
+
+- Label with **what is communicated**: `"User queries"`, `"Auth tokens"`, `"Log data"`
+- Protocol can be parenthetical: `"Queries (gRPC)"`
+- Simpler arrows than DFD — use `-->` without requiring bidirectional flows
+
+### Kubernetes Pods in Architecture Diagrams
+
+Show pods with their full container composition:
+```
+inf["Inferencing Flow
+ MISE + Dapr"]:::service
+ing["Ingestion Flow
+ MISE + Dapr"]:::service
+```
+
+### Key Difference from DFD
+
+The architecture diagram shows **what the system does** (logical components and interactions). The threat model DFD shows **what could be attacked** (trust boundaries, data flows with protocols, element types). They share many components but serve different purposes.
+
+### Complete Architecture Diagram Template
+
+```mermaid
+%%{init: {'theme': 'base', 'themeVariables': { 'background': '#ffffff', 'primaryColor': '#ffffff', 'lineColor': '#666666' }}}%%
+flowchart LR
+ classDef service fill:#6baed6,stroke:#2171b5,stroke-width:2px,color:#000000
+ classDef external fill:#fdae61,stroke:#d94701,stroke-width:2px,color:#000000
+ classDef datastore fill:#74c476,stroke:#238b45,stroke-width:2px,color:#000000
+ linkStyle default stroke:#666666,stroke-width:2px
+
+ User(["User"]):::external
+
+ subgraph Backend["Backend Services"]
+ Api["API Service"]:::service
+ Worker["Worker"]:::service
+ end
+
+ subgraph Data["Data Layer"]
+ Db[("Database")]:::datastore
+ Cache[("Cache")]:::datastore
+ end
+
+ User -->|"HTTPS"| Api
+ Api --> Worker
+ Worker --> Db
+ Api --> Cache
+
+ style Backend fill:#f0f4ff,stroke:#2171b5,stroke-width:2px,stroke-dasharray: 5 5
+ style Data fill:#f0fff0,stroke:#238b45,stroke-width:2px,stroke-dasharray: 5 5
+```
+
+### Kubernetes Architecture Template
+
+```mermaid
+%%{init: {'theme': 'base', 'themeVariables': { 'background': '#ffffff', 'primaryColor': '#ffffff', 'lineColor': '#666666' }}}%%
+flowchart LR
+ classDef service fill:#6baed6,stroke:#2171b5,stroke-width:2px,color:#000000
+ classDef external fill:#fdae61,stroke:#d94701,stroke-width:2px,color:#000000
+ classDef datastore fill:#74c476,stroke:#238b45,stroke-width:2px,color:#000000
+ linkStyle default stroke:#666666,stroke-width:2px
+
+ User(["User"]):::external
+ IdP(["Azure AD"]):::external
+
+ subgraph K8s["Kubernetes Cluster"]
+ Inf["Inferencing Flow
+ MISE + Dapr"]:::service
+ Ing["Ingestion Flow
+ MISE + Dapr"]:::service
+ Redis[("Redis")]:::datastore
+ end
+
+ User -->|"HTTPS"| Inf
+ Inf -->|"Auth (MISE)"| IdP
+ Ing -->|"Auth (MISE)"| IdP
+ Inf -->|"State (Dapr)"| Redis
+
+ style K8s fill:#f0f4ff,stroke:#2171b5,stroke-width:2px,stroke-dasharray: 5 5
+```
+
+---
+
+## Sequence Diagram Rules
+
+Used in: `0.1-architecture.md` top scenarios
+
+- The **first 3 scenarios MUST** each include a Mermaid `sequenceDiagram`
+- Scenarios 4-5 may optionally include one
+- Use the **Sequence Diagram Init Block** above at the top of each
+- Use `participant` aliases matching the Key Components table
+- Show activations (`activate`/`deactivate`) for request-response patterns
+- Include `Note` blocks for security-relevant steps (e.g., "Validates JWT token")
+- Keep diagrams focused — core workflow, not every error path
+
+### Complete Sequence Diagram Example
+
+```mermaid
+%%{init: {'theme': 'base', 'themeVariables': {
+ 'background': '#ffffff',
+ 'actorBkg': '#6baed6', 'actorBorder': '#2171b5', 'actorTextColor': '#000000',
+ 'signalColor': '#666666', 'signalTextColor': '#666666',
+ 'noteBkgColor': '#fdae61', 'noteBorderColor': '#d94701', 'noteTextColor': '#000000',
+ 'activationBkgColor': '#ddeeff', 'activationBorderColor': '#2171b5'
+}}}%%
+sequenceDiagram
+ actor User
+ participant Api as API Service
+ participant Db as Database
+
+ User->>Api: POST /resource
+ activate Api
+ Note over Api: Validates JWT token
+ Api->>Db: INSERT query
+ Db-->>Api: Result
+ Api-->>User: 201 Created
+ deactivate Api
+```
+
+---
+
+## Summary Diagram Rules
+
+Used in: `1.2-threatmodel-summary.mmd` (generated only when detailed diagram has >15 elements or >4 trust boundaries)
+
+1. **All trust boundaries must be preserved** — never combine or omit
+2. **Only combine components that are NOT**: entry points, core flow components, security-critical services, primary data stores
+3. **Candidates for aggregation**: supporting infrastructure, secondary caches, multiple externals at same trust level
+4. **Combined element labels must list contents:**
+ ```
+ DataLayer[("Data Layer
(UserDB, OrderDB, Redis)")]
+ SupportServices(("Supporting
(Logging, Monitoring)"))
+ ```
+5. Use `SDF` prefix for summary data flows: `SDF01`, `SDF02`, ...
+6. Include mapping table in `1-threatmodel.md`:
+ ```
+ | Summary Element | Contains | Summary Flows | Maps to Detailed Flows |
+ ```
+
+---
+
+## Naming Conventions
+
+| Item | Convention | Example |
+|------|-----------|---------|
+| Element ID | PascalCase, no spaces | `WebApi`, `UserDb` |
+| Display Name | Human readable in quotes | `"Web API"`, `"User Database"` |
+| Flow Label | Protocol or action in quotes | `"HTTPS"`, `"SQL"`, `"gRPC"` |
+| Flow ID | Unique short identifier | `DF01`, `DF02` |
+| Boundary ID | PascalCase | `InternalNetwork`, `PublicDMZ` |
+
+**CRITICAL: Always quote ALL text in Mermaid diagrams:**
+- Element labels: `["Name"]`, `(("Name"))`, `[("Name")]`
+- Flow labels: `-->|"Label"|`
+- Subgraph titles: `subgraph ID["Title"]`
+
+---
+
+## Quick Reference - Shapes
+
+```
+External Interactor: ["Name"] → Rectangle
+Process: (("Name")) → Circle (double parentheses)
+Data Store: [("Name")] → Cylinder
+```
+
+## Quick Reference - Flows
+
+```
+Unidirectional: A -->|"Label"| B
+Bidirectional: A <-->|"Label"| B
+```
+
+## Quick Reference - Boundaries
+
+```
+subgraph BoundaryId["Display Name"]
+ %% elements inside
+end
+style BoundaryId fill:none,stroke:#e31a1c,stroke-width:3px,stroke-dasharray: 5 5
+```
+
+---
+
+## STRIDE Analysis — Sidecar Implications
+
+Although sidecars are NOT separate diagram nodes, they DO appear in STRIDE analysis:
+
+- Sidecars with distinct threat surfaces (e.g., MISE auth bypass, Dapr mTLS) get their own `## Component` section in `2-stride-analysis.md`
+- The component heading notes which pods they are co-located in
+- Threats related to intra-pod communication (localhost bypass, shared namespace) go under the **primary container's** component section
+- **Pod Co-location** line in STRIDE template: list co-located sidecars (e.g., "MISE Sidecar, Dapr Sidecar")
diff --git a/skills/threat-model-analyst/references/incremental-orchestrator.md b/skills/threat-model-analyst/references/incremental-orchestrator.md
new file mode 100644
index 00000000..d11082b4
--- /dev/null
+++ b/skills/threat-model-analyst/references/incremental-orchestrator.md
@@ -0,0 +1,708 @@
+# Incremental Orchestrator — Threat Model Update Workflow
+
+This file contains the complete orchestration logic for performing an **incremental threat model analysis** — generating a new threat model report that builds on an existing baseline report. It is invoked when the user requests an updated analysis and a prior `threat-model-*` folder exists.
+
+**Key difference from single analysis (`orchestrator.md`):** Instead of discovering components from scratch, this workflow inherits the old report's component inventory, IDs, and conventions. It then verifies each item against the current code and discovers new items.
+
+## ⚡ Context Budget — Read Files Selectively
+
+**Phase 1 (setup + change detection):** Read this file (`incremental-orchestrator.md`) only. The old `threat-inventory.json` provides the structural skeleton — no need to read other skill files yet.
+**Phase 2 (report generation):** Read `orchestrator.md` (for mandatory rules 1–34), `output-formats.md`, `diagram-conventions.md` — plus the relevant skeleton from `skeletons/` before writing each file. See the incremental-specific rules below.
+**Phase 3 (verification):** Delegate to a sub-agent with `verification-checklist.md` (all 9 phases, including Phase 8 for comparison HTML).
+
+---
+
+## When to Use This Workflow
+
+Use incremental analysis when ALL of these conditions are met:
+1. The user's request involves updating, re-running, or refreshing a threat model
+2. A prior `threat-model-*` folder exists in the repository with a valid `threat-inventory.json`
+3. The user provides or implies both: a baseline report folder AND a target commit (defaults to HEAD)
+
+**Trigger examples:**
+- "Update the threat model using threat-model-20260309-174425 as the baseline"
+- "Run an incremental threat model analysis against the previous report"
+- "What changed security-wise since the last threat model?"
+- "Refresh the threat model for the latest commit"
+
+**NOT this workflow:**
+- First-time analysis (no baseline) → use `orchestrator.md`
+- "Analyze the security of this repo" with no mention of a prior report → use `orchestrator.md`
+
+---
+
+## Inputs
+
+| Input | Source | Required? |
+|-------|--------|-----------|
+| Baseline report folder | Path to `threat-model-*` directory | Yes |
+| Baseline `threat-inventory.json` | `{baseline_folder}/threat-inventory.json` | Yes |
+| Baseline commit SHA | From `{baseline_folder}/0-assessment.md` Report Metadata | Yes |
+| Target commit | User-provided SHA or defaults to HEAD | Yes (default: HEAD) |
+
+---
+
+**⛔ Sub-Agent Governance applies to ALL phases.** See `orchestrator.md` Sub-Agent Governance section. Sub-agents are READ-ONLY helpers — they NEVER call `create_file` for report files.
+
+## Phase 0: Setup & Validation
+
+1. **Record start time:**
+ ```
+ Get-Date -Format "yyyy-MM-dd HH:mm:ss" -AsUTC
+ ```
+ Store as `START_TIME`.
+
+2. **Gather git info:**
+ ```
+ git remote get-url origin
+ git branch --show-current
+ git rev-parse --short HEAD
+ hostname
+ ```
+
+3. **Validate inputs:**
+ - Confirm baseline folder exists: `Test-Path {baseline_folder}/threat-inventory.json`
+ - Read baseline commit SHA from `0-assessment.md`: search for `| Git Commit |` row
+ - Confirm target commit is resolvable: `git rev-parse {target_sha}`
+ - **Get commit dates:** `git log -1 --format="%ai" {baseline_sha}` and `git log -1 --format="%ai" {target_sha}` — NOT today's date
+ - **Get code change counts** (for HTML metrics bar):
+ ```
+ git rev-list --count {baseline_sha}..{target_sha}
+ git log --oneline --merges --grep="Merged PR" {baseline_sha}..{target_sha} | wc -l
+ ```
+ Store as `COMMIT_COUNT` and `PR_COUNT`.
+
+4. **Baseline code access — reuse or create worktree:**
+ ```
+ # Check for existing worktree
+ git worktree list
+
+ # If a worktree for baseline_sha exists → reuse it
+ # Verify: git -C {worktree_path} rev-parse HEAD
+
+ # If not → create one:
+ git worktree add ../baseline-{baseline_sha_short} {baseline_sha}
+ ```
+ Store the worktree path as `BASELINE_WORKTREE` for old-code verification in later phases.
+
+5. **Create output folder:**
+ ```
+ threat-model-{YYYYMMDD-HHmmss}/
+ ```
+
+---
+
+## Phase 1: Load Old Report Skeleton
+
+Read the baseline `threat-inventory.json` and extract the structural skeleton:
+
+```
+From threat-inventory.json, load:
+ - components[] → all component IDs, types, boundaries, source_files, fingerprints
+ - flows[] → all flow IDs, from/to, protocols
+ - boundaries[] → all boundary IDs, contains lists
+ - threats[] → all threat IDs, component mappings, stride categories, tiers
+ - findings[] → all finding IDs, titles, severities, CWEs, component mappings
+ - metrics → totals for validation
+
+Store as the "inherited inventory" — the structural foundation.
+```
+
+**Do NOT read the full prose** from the old report's markdown files yet. Only load structured data. Read old report prose on-demand when:
+- Verifying if a specific code pattern was previously analyzed
+- Resolving ambiguity about a component's role or classification
+- Historical context needed for a finding status decision
+
+---
+
+## Phase 2: Per-Component Change Detection
+
+For each component in the inherited inventory, determine its change status:
+
+```
+For EACH component in inherited inventory:
+
+ 1. Check source_files existence at target commit:
+ git ls-tree {target_sha} -- {each source_file}
+
+ 2. If ALL source files missing:
+ → change_status = "removed"
+ → Mark all linked threats as "removed_with_component"
+ → Mark all linked findings as "removed_with_component"
+
+ 3. If source files exist, check for changes:
+ git diff --stat {baseline_sha} {target_sha} -- {source_files}
+
+ If NO changes → change_status = "unchanged"
+
+ If changes exist, check if security-relevant:
+ Read the diff: git diff {baseline_sha} {target_sha} -- {source_files}
+ Look for changes in:
+ - Auth/credential patterns (tokens, passwords, certificates)
+ - Network/API surface (new endpoints, changed listeners, port bindings)
+ - Input validation (sanitization, parsing, deserialization)
+ - Command execution patterns (shell exec, process spawn)
+ - Config values (TLS settings, CORS, security headers)
+ - Dependencies (new packages, version changes)
+
+ If security-relevant → change_status = "modified"
+ If cosmetic only (whitespace, comments, logging, docs) → change_status = "unchanged"
+
+ 4. If files moved or renamed:
+ git log --follow --diff-filter=R {baseline_sha}..{target_sha} -- {source_files}
+ → change_status = "restructured"
+ → Update source_file references to new paths
+```
+
+**Record the classification for every component** — this drives all downstream decisions.
+
+---
+
+## Phase 3: Scan for New Components
+
+```
+1. Enumerate source directories/files at {target_sha} that are NOT referenced
+ by any existing component's source_files or source_directories.
+ Focus on: new top-level directories, new *Service.cs/*Agent.cs/*Server.cs classes,
+ new Helm deployments, new API controllers.
+
+2. Apply the same component discovery rules from orchestrator.md:
+ - Class-anchored naming (PascalCase from actual class names)
+ - Component eligibility criteria (crosses trust boundary or handles security data)
+ - Same naming procedure (primary class → script → config → directory → technology)
+
+3. For each candidate new component:
+ - Verify it didn't exist at baseline: git ls-tree {baseline_sha} -- {path}
+ - If it existed at baseline → this is a "missed component" from the old analysis
+ → Add to Needs Verification section with note: "Component existed at baseline
+ but was not in the previous analysis. May indicate an analysis gap."
+ - If genuinely new (files didn't exist at baseline):
+ → change_status = "new"
+ → Assign a new component ID following the same PascalCase naming rules
+ → Full STRIDE analysis will be performed in Phase 4
+```
+
+---
+
+## Phase 4: Generate Report Files
+
+Now generate all report files. **Read the relevant skill files before starting:**
+- `orchestrator.md` — mandatory rules 1–34 apply to all report files
+- `output-formats.md` — templates and format rules
+- `diagram-conventions.md` — diagram colors and styles
+- **Before writing EACH file, read the corresponding skeleton from `skeletons/skeleton-*.md`** — copy VERBATIM and fill `[FILL]` placeholders
+
+**⛔ SUB-AGENT GOVERNANCE (MANDATORY — prevents the dual-folder bug):** The parent agent owns ALL file creation. Sub-agents are READ-ONLY helpers that search code, gather context, and run verification — they NEVER call `create_file` for report files. See the full Sub-Agent Governance rules in `orchestrator.md`. The ONLY exception is `threat-inventory.json` delegation for large repos — and even then, the sub-agent prompt must include the exact output file path and explicit instruction to write ONLY that one file.
+
+**⛔ CRITICAL: The incremental report is a STANDALONE report.** Someone reading it without the old report must understand the complete security posture. Status annotations ([STILL PRESENT], [FIXED], [NEW CODE], etc.) are additions on top of complete content — not replacements for it.
+
+### 4a. 0.1-architecture.md
+
+- **Read `skeletons/skeleton-architecture.md` first** — use as structural template
+- Copy the old report's component structure as your starting template
+- **Unchanged components:** Regenerate description using the current code (not copy-paste from old report). Same ID, same conventions.
+- **Modified components:** Update description to reflect code changes. Add annotation: `[MODIFIED — security-relevant changes detected]`
+- **New components:** Add with annotation: `[NEW]`
+- **Removed components:** Add with annotation: `[REMOVED]` and brief note
+- Tech stack, deployment model: update if changed, otherwise carry forward
+
+ ⛔ **DEPLOYMENT CLASSIFICATION IS MANDATORY (even in incremental mode):**
+ The `0.1-architecture.md` MUST contain:
+ 1. `**Deployment Classification:** \`[VALUE]\`` line (e.g., `K8S_SERVICE`, `LOCALHOST_DESKTOP`)
+ 2. `### Component Exposure Table` with columns: Component, Listens On, Auth Required, Reachability, Min Prerequisite, Derived Tier
+ If the baseline had these, carry them forward and update for new/modified components.
+ If the baseline did NOT have these, **derive them from code NOW** — they are required for all subsequent steps.
+ **DO NOT proceed to Step 4b without these two elements in place.**
+
+- Scenarios: keep old scenarios, add new ones for new functionality
+- All standard `0.1-architecture.md` rules from `output-formats.md` apply
+
+### 4b. 1.1-threatmodel.mmd (DFD)
+
+- **Read `skeletons/skeleton-dfd.md` and `skeletons/skeleton-summary-dfd.md` first**
+- Start from the old DFD's logical layout
+- **Same node IDs** for carried-forward components (critical for ID stability)
+- **New components:** Add with distinctive styling — use `classDef newComponent fill:#d4edda,stroke:#28a745,stroke-width:3px`
+- **Removed components:** Show as dashed with gray fill — use `classDef removedComponent fill:#e9ecef,stroke:#6c757d,stroke-width:1px,stroke-dasharray:5`
+- **Same flow IDs** for unchanged flows
+- **New flows:** New IDs continuing the sequence
+- All standard DFD rules from `diagram-conventions.md` apply (flowchart LR, color palette, etc.)
+
+ ⛔ **POST-DFD GATE:** After creating `1.1-threatmodel.mmd`, count elements and boundaries. If elements > 15 OR boundaries > 4 → create `1.2-threatmodel-summary.mmd` using `skeleton-summary-dfd.md` NOW. Do NOT proceed to Step 4c until the decision is made.
+
+### 4c. 1-threatmodel.md
+
+- **Read `skeletons/skeleton-threatmodel.md` first** — use table structure
+- Element table: all old elements + new elements, with an added `Status` column
+ - Values: `Unchanged`, `Modified`, `New`, `Removed`, `Restructured`
+- Flow table: all old flows + new flows, with `Status` column
+- Boundary table: inherited boundaries + any new ones
+- If `1.2-threatmodel-summary.mmd` was generated, include `## Summary View` section with the summary diagram and mapping table
+- All standard table rules from `output-formats.md` apply
+
+### 4d. 2-stride-analysis.md
+
+- **Read `skeletons/skeleton-stride-analysis.md` first** — use Summary table and per-component structure
+
+**⛔ CRITICAL REMINDERS FOR INCREMENTAL STRIDE (these rules from `orchestrator.md` apply identically here):**
+1. **The "A" in STRIDE-A is ALWAYS "Abuse"** (business logic abuse, workflow manipulation, feature misuse). NEVER use "Authorization" as the STRIDE-A category name. This applies to threat ID suffixes (T01.A), N/A justification labels, and all prose. Authorization issues fall under Elevation of Privilege (E), not the A category.
+2. **The `## Summary` table MUST appear at the TOP of the file**, immediately after `## Exploitability Tiers`, BEFORE any individual component sections. Use this EXACT structure at the top:
+
+```markdown
+# STRIDE-A Threat Analysis
+
+## Exploitability Tiers
+| Tier | Label | Prerequisites | Assignment Rule |
+|------|-------|---------------|----------------|
+| **Tier 1** | Direct Exposure | `None` | Exploitable by unauthenticated external attacker with NO prior access. |
+| **Tier 2** | Conditional Risk | Single prerequisite | Requires exactly ONE form of access. |
+| **Tier 3** | Defense-in-Depth | Multiple prerequisites or infrastructure access | Requires significant prior breach or multiple combined prerequisites. |
+
+## Summary
+| Component | Link | S | T | R | I | D | E | A | Total | T1 | T2 | T3 | Risk |
+|-----------|------|---|---|---|---|---|---|---|-------|----|----|----|------|
+
+
+---
+## [First Component Name]
+```
+
+3. **STRIDE categories may produce 0, 1, 2, 3+ threats** per component. Do NOT cap at 1 threat per category. Components with rich security surfaces should typically have 2-4 threats per relevant category. If every STRIDE cell in the Summary table is 0 or 1, the analysis is too shallow — go back and identify additional threat vectors. The Summary table columns reflect actual threat counts.
+4. **⛔ PREREQUISITE FLOOR CHECK (per threat):** Before assigning a prerequisite to any threat, look up the component's `Min Prerequisite` and `Derived Tier` in the Component Exposure Table (`0.1-architecture.md`). The threat's prerequisite MUST be ≥ the component's floor. The threat's tier MUST be ≥ the component's derived tier. Use the canonical prerequisite→tier mapping from `analysis-principles.md`. Prerequisites MUST use only canonical values: `None`, `Authenticated User`, `Privileged User`, `Internal Network`, `Local Process Access`, `Host/OS Access`, `Admin Credentials`, `Physical Access`, `{Component} Compromise`. ⛔ `Application Access` and `Host Access` are FORBIDDEN.
+
+**⛔ HEADING ANCHOR RULE (applies to ALL output files):** ALL `##` and `###` headings in every output file must be PLAIN text — NO status tags (`[Existing]`, `[Fixed]`, `[Partial]`, `[New]`, `[Removed]`, or any old-style tags) in heading text. Tags break markdown anchor links and pollute table-of-contents. Place status annotations on the FIRST LINE of the section/finding body instead:
+- ✅ `## KmsPluginProvider` with first line `> **[New]** Component added in this release.`
+- ✅ `### FIND-01: Missing Auth Check` with first line `> **[Existing]**`
+- ❌ `## KmsPluginProvider [New]` (breaks `#kmspluginprovider` anchor)
+- ❌ `### FIND-01: Missing Auth Check [Existing]` (pollutes heading)
+
+This rule applies to: `0.1-architecture.md`, `2-stride-analysis.md`, `3-findings.md`, `1-threatmodel.md`.
+
+For each component, the STRIDE analysis approach depends on its change status:
+
+| Component Status | STRIDE Approach |
+|-----------------|-----------------|
+| **Unchanged** | Carry forward all threat entries from old report with `[STILL PRESENT]` annotation. Re-verify each threat's mitigation status against current code. |
+| **Modified** | Re-analyze the component with access to the diff. For each old threat: determine if `still_present`, `fixed`, `mitigated`, or `modified`. Discover new threats from the code changes → classify as `new_in_modified`. |
+| **New** | Full fresh STRIDE-A analysis (same as single-analysis mode). All threats classified as `new_code`. |
+| **Removed** | Section header with note: "Component removed — all threats resolved with `removed_with_component` status." |
+
+**Threat ID continuity:**
+- Old threats keep their original IDs (e.g., T01.S, T02.T)
+- New threats continue the sequence from the old report's highest threat number
+- NEVER reassign or reuse an old threat ID
+
+**N/A categories (from §3.7 of PRD):**
+- Each component gets all 7 STRIDE-A categories addressed
+- Non-applicable categories: `N/A — {1-sentence justification}`
+- N/A entries do NOT count toward threat totals
+
+**Status annotation format in STRIDE tables:**
+Add a `Change` column to each threat table row with one of:
+- `Existing` — threat exists in current code, same as before (includes threats with minor detail changes)
+- `Fixed` — vulnerability was remediated (cite the specific code change)
+- `New` — threat from a new component, code change, or previously unidentified
+- `Removed` — component was removed
+
+
+
+⛔ POST-STEP CHECK: After writing the Change column for ALL threats, verify:
+ 1. Every threat row has exactly one of: Existing, Fixed, New, Removed
+ 2. No old-style tags: Still Present, New (Code), New (Modified), Previously Unidentified
+ 3. Fixed threats cite the specific code change
+
+### 4e. 3-findings.md
+
+⛔ **BEFORE WRITING ANY FINDING — Re-read `skeletons/skeleton-findings.md` NOW.**
+The skeleton defines the EXACT structure for each finding block, including the mandatory `**Prerequisite basis:**` line in the `#### Evidence` section. Every finding — whether [Existing], [New], [Fixed], or [Partial] — MUST follow this skeleton structure.
+
+⛔ **DEPLOYMENT CONTEXT GATE (FAIL-CLOSED) — applies to ALL findings (new and carried-forward):**
+Read `0.1-architecture.md` Deployment Classification and Component Exposure Table.
+If classification is `LOCALHOST_DESKTOP` or `LOCALHOST_SERVICE`:
+- ZERO findings may have `Exploitation Prerequisites` = `None` → fix to `Local Process Access` or `Host/OS Access`
+- ZERO findings may be in `## Tier 1` → downgrade to T2/T3
+- ZERO CVSS vectors may use `AV:N` unless component has `Reachability = External`
+For ALL classifications:
+- Each finding's prerequisite MUST be ≥ its component's `Min Prerequisite` from the exposure table
+- Each finding's tier MUST be ≥ its component's `Derived Tier`
+- **EVERY finding's `#### Evidence` section MUST start with a `**Prerequisite basis:**` line** citing the specific code/config that determines the prerequisite (e.g., "ClusterIP service, no Ingress — Internal Only per Exposure Table"). This applies to [Existing] findings too — re-derive from current code.
+- Prerequisites MUST use only canonical values. ⛔ `Application Access` and `Host Access` are FORBIDDEN.
+
+For each old finding, verify against the current code:
+
+| Situation | change_status | Action |
+|-----------|---------------|--------|
+| Code unchanged, vulnerability intact | `still_present` | Carry forward with `> **[Existing]**` on first line of body |
+| Code changed to fix the vulnerability | `fixed` | Mark with `> **[Fixed]**`, cite the specific code change |
+| Code changed partially | `partially_mitigated` | Mark with `> **[Partial]**`, explain what changed and what remains |
+| Component removed entirely | `removed_with_component` | Mark with `> **[Removed]**` |
+
+For new findings:
+
+| Situation | change_status | Label |
+|-----------|---------------|-------|
+| New component, new vulnerability | `new_code` | `> **[New]**` |
+| Existing component, vulnerability introduced by code change | `new_in_modified` | `> **[New]**` — cite the specific change |
+| Existing component, vulnerability was in old code but missed | `previously_unidentified` | `> **[New]**` — verify against baseline worktree |
+
+
+
+**Finding ID continuity:**
+- Old findings keep their original IDs (FIND-01 through FIND-N)
+- New findings continue the sequence: FIND-N+1, FIND-N+2, ...
+- No gaps, no duplicates
+- Fixed findings are retained but annotated — they are NOT removed from the report
+- **Document order**: Findings are sorted by Tier (1→2→3), then by severity (Critical→Important→Moderate→Low), then by CVSS descending — same as standalone analysis. Because old IDs are preserved, the ID numbers may NOT be numerically ascending in the document. This is acceptable in incremental mode — ID stability for cross-report tracing takes precedence over sequential ordering. The `### FIND-XX:` headings will appear in tier/severity order, not ID order.
+
+**Previously-unidentified verification procedure:**
+1. Identify the finding's component and evidence files
+2. Read the same files at the baseline commit: `cat {BASELINE_WORKTREE}/{file_path}`
+3. If the vulnerability pattern exists in the old code → `previously_unidentified`
+4. If the vulnerability pattern does NOT exist in the old code → `new_in_modified`
+
+### 4f. threat-inventory.json
+
+- **Read `skeletons/skeleton-inventory.md` first** — use exact field names and schema structure
+
+Same schema as single analysis, with additional fields:
+
+```json
+{
+ "schema_version": "1.1",
+ "incremental": true,
+ "baseline_report": "threat-model-20260309-174425",
+ "baseline_commit": "2dd84ab",
+ "target_commit": "abc1234",
+
+ "components": [
+ {
+ "id": "McpHost",
+ "change_status": "unchanged",
+ ...existing fields...
+ }
+ ],
+
+ "threats": [
+ {
+ "id": "T01.S",
+ "change_status": "still_present",
+ ...existing fields...
+ }
+ ],
+
+ "findings": [
+ {
+ "id": "FIND-01",
+ "change_status": "still_present",
+ ...existing fields...
+ }
+ ],
+
+ "metrics": {
+ ...existing fields...,
+ "status_summary": {
+ "components": {
+ "unchanged": 15,
+ "modified": 2,
+ "new": 1,
+ "removed": 1,
+ "restructured": 0
+ },
+ "threats": {
+ "still_present": 80,
+ "fixed": 5,
+ "mitigated": 3,
+ "new_code": 10,
+ "new_in_modified": 4,
+ "previously_unidentified": 2,
+ "removed_with_component": 8
+ },
+ "findings": {
+ "still_present": 12,
+ "fixed": 2,
+ "partially_mitigated": 1,
+ "new_code": 3,
+ "new_in_modified": 2,
+ "previously_unidentified": 1,
+ "removed_with_component": 1
+ }
+ }
+ }
+}
+```
+
+### 4g. 0-assessment.md
+
+- **Read `skeletons/skeleton-assessment.md` first** — use section order and table structures
+
+Standard assessment sections (all 7 mandatory) plus incremental-specific sections:
+
+**Standard sections (same as single analysis):**
+1. Report Files
+2. Executive Summary (with `> **Note on threat counts:**` blockquote)
+3. Action Summary (with `### Quick Wins`)
+4. Analysis Context & Assumptions (with `### Needs Verification` and `### Finding Overrides`)
+5. References Consulted
+6. Report Metadata
+7. Classification Reference (static table copied from skeleton)
+
+**Additional incremental sections (insert between Action Summary and Analysis Context):**
+
+```markdown
+## Change Summary
+
+### Component Changes
+| Status | Count | Components |
+|--------|-------|------------|
+| Unchanged | X | ComponentA, ComponentB, ... |
+| Modified | Y | ComponentC, ... |
+| New | Z | ComponentD, ... |
+| Removed | W | ComponentE, ... |
+
+### Threat Status
+| Status | Count |
+|--------|-------|
+| Still Present | X |
+| Fixed | Y |
+| New (Code) | Z |
+| New (Modified) | M |
+| Previously Unidentified | W |
+| Removed with Component | V |
+
+### Finding Status
+| Status | Count |
+|--------|-------|
+| Still Present | X |
+| Fixed | Y |
+| Partially Mitigated | P |
+| New (Code) | Z |
+| New (Modified) | M |
+| Previously Unidentified | W |
+| Removed with Component | V |
+
+### Risk Direction
+[Improving / Worsening / Stable] — [1-2 sentence justification based on status distribution]
+
+---
+
+## Previously Unidentified Issues
+
+These vulnerabilities were present in the baseline code at commit `{baseline_sha}` but were not identified in the prior analysis:
+
+| Finding | Title | Component | Evidence |
+|---------|-------|-----------|----------|
+| FIND-XX | [title] | [component] | Baseline code at `{file}:{line}` |
+```
+
+**Report Metadata additions:**
+```markdown
+| Baseline Report | `{baseline_folder}` |
+| Baseline Commit | `{baseline_sha}` (`{baseline_commit_date}` — run `git log -1 --format="%cs" {baseline_sha}`) |
+| Target Commit | `{target_sha}` (`{target_commit_date}` — run `git log -1 --format="%cs" {target_sha}`) |
+| Baseline Worktree | `{worktree_path}` |
+| Analysis Mode | `Incremental` |
+```
+
+### 4h. incremental-comparison.html
+
+- **Read `skeletons/skeleton-incremental-html.md` first** — use 8-section structure and CSS variables
+
+Generate a self-contained HTML file that visualizes the comparison. All data comes from the `change_status` fields already computed in `threat-inventory.json`.
+
+**Structure:**
+
+```html
+
+
| Component | +S | T | R | I | D | E | A | +Total | ++ | T1 | T2 | T3 | +
|---|