
Threat Model Analyst
- 1.2k installs
- 37.1k repo stars
- Updated July 28, 2026
- github/awesome-copilot
threat-model-analyst is an agent skill that runs STRIDE-A threat modeling or incremental threat model updates with DFD diagrams, findings, and executive assessments.
About
The threat-model-analyst skill performs repository and system security audits using STRIDE-A threat modeling, Zero Trust principles, and defense-in-depth analysis. It supports single-analysis mode with a ten-step orchestrator producing architecture overviews, data-flow diagrams, STRIDE-A tables, prioritized findings, and executive assessments, and incremental mode that diffs a prior threat-model report against the latest code with new, resolved, and still-present threat tracking plus embedded HTML comparison. Activation requires explicit user request for threat modeling or direct invocation. Reference files cover orchestrator rules, incremental orchestrator workflow, analysis principles, diagram conventions, output format templates, verbatim skeletons, verification checklists, and TMT element taxonomy for trust boundaries. Sub-agent governance and verify-before-flagging rules reduce false positives on secrets and platform defaults. Developers use it when generating a full threat model, refreshing an existing report, or comparing security posture between commits or report folders.
- Full STRIDE-A single analysis and incremental update modes with explicit activation gates.
- Ten-step orchestrator with architecture, DFD, STRIDE-A, findings, and assessment outputs.
- Incremental mode diffs prior threat-model folders with HTML comparison embed.
- Reference library for diagram conventions, skeletons, and verification checklists.
- Verify-before-flagging rules for secrets, boundaries, and OWASP-aligned severity.
Threat Model Analyst by the numbers
- 1,242 all-time installs (skills.sh)
- +26 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #347 of 2,209 Security skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
threat-model-analyst capabilities & compatibility
- Capabilities
- stride a single repository analysis · incremental threat model diffing · dfd and architecture diagram generation · prioritized findings and executive assessment · verification checklist and skeleton driven outpu · tmt element taxonomy mapping
- Use cases
- security audit · code review · research
What threat-model-analyst says it does
You perform security audits using STRIDE-A (STRIDE + Abuse) threat modeling
Only activate when the user explicitly requests a threat model analysis
npx skills add https://github.com/github/awesome-copilot --skill threat-model-analystAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.2k |
|---|---|
| repo stars | ★ 37.1k |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 28, 2026 |
| Repository | github/awesome-copilot ↗ |
How do I produce or refresh a structured STRIDE-A threat model with architecture diagrams and prioritized security findings for a repository?
Run full STRIDE-A threat modeling or incremental threat model updates with DFD diagrams, findings, and executive assessments.
Who is it for?
Security-minded developers explicitly requesting STRIDE-A threat modeling or incremental updates on an existing report baseline.
Skip if: Skip for casual code review without threat modeling scope or when the user did not request threat model analysis.
When should I use this skill?
User explicitly asks for threat model analysis, incremental threat model update, or invokes /threat-model-analyst.
What you get
A threat-model report folder with architecture, STRIDE-A analysis, findings, assessment files, and optional incremental HTML comparison.
- Evidence-backed threat findings
- Verified control inventory
- Ruled-out false positive list
By the numbers
- Uses a mandatory three-step verify-before-flagging process for every security finding
- References 4 infrastructure categories: CAs, service meshes, policy engines, and secret managers
Files
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 athreat-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 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 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 — 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 | Always — read first | Complete 10-step workflow, 34 mandatory rules, sub-agent governance, tool usage, verification process |
| Incremental Orchestrator | Incremental/update analyses | Complete incremental workflow: load old skeleton, change detection, generate report with status annotations, HTML comparison |
| Analysis Principles | Analyzing code for security issues | Verify-before-flagging rules, security infrastructure inventory, OWASP Top 10:2025, platform defaults, exploitability tiers, severity standards |
| Diagram Conventions | Creating ANY Mermaid diagram | Color palette, shapes, sidecar co-location rules, pre-render checklist, DFD vs architecture styles, sequence diagram styles |
| Output Formats | 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 | 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 | 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 | 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 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
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
## Componentsection in2-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 Platformentry 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, orPlatformonly. - 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
Openwith 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 =
Nonefor 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,/metricswithout 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 Networkto 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:Nif 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)
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<br/>+ MISE, Dapr")):::process
IngestionFlow(("Ingestion Flow<br/>+ MISE, Dapr")):::process
VectorDbApi(("VectorDB API<br/>+ Dapr")):::processDO NOT DO THIS — never create standalone sidecar nodes:
❌ MISE(("MISE Sidecar")):::process
❌ DaprSidecar(("Dapr Sidecar")):::process
❌ InferencingFlow -->|"localhost"| MISEWhy: 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"| MISEIntra-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"| RedisIf 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
<br/>+ SidecarNamefor 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
subgraphhas a closingend - [ ] 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:2pxSequence 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 5Flow Labels
Unidirectional: A -->|"Label"| B
Bidirectional: A <-->|"Label"| BData Flow IDs
- Detailed flows:
DF01,DF02,DF03... - Summary flows:
SDF01,SDF02,SDF03...
Complete DFD Template
%%{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 5Kubernetes DFD Template (With Sidecars)
%%{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<br/>+ AuthProxy, Dapr")):::process
Worker(("Worker<br/>+ 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 5Key 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 containerApiService -->|"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 5Layer 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<br/>+ MISE + Dapr"]:::service
ing["Ingestion Flow<br/>+ MISE + Dapr"]:::serviceKey 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
%%{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 5Kubernetes Architecture Template
%%{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<br/>+ MISE + Dapr"]:::service
Ing["Ingestion Flow<br/>+ 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
participantaliases matching the Key Components table - Show activations (
activate/deactivate) for request-response patterns - Include
Noteblocks for security-relevant steps (e.g., "Validates JWT token") - Keep diagrams focused — core workflow, not every error path
Complete Sequence Diagram Example
%%{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<br/>(UserDB, OrderDB, Redis)")]
SupportServices(("Supporting<br/>(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")] → CylinderQuick Reference - Flows
Unidirectional: A -->|"Label"| B
Bidirectional: A <-->|"Label"| BQuick 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
## Componentsection in2-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")
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" -AsUTCStore as START_TIME.
2. Gather git info:
git remote get-url origin
git branch --show-current
git rev-parse --short HEAD
hostname3. 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}andgit 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 -lStore 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 pathsRecord 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 filesoutput-formats.md— templates and format rulesdiagram-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.mdrules fromoutput-formats.mdapply
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.mdapply (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
Statuscolumn - Values:
Unchanged,Modified,New,Removed,Restructured - Flow table: all old flows + new flows, with
Statuscolumn - Boundary table: inherited boundaries + any new ones
- If
1.2-threatmodel-summary.mmdwas generated, include## Summary Viewsection with the summary diagram and mapping table - All standard table rules from
output-formats.mdapply
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:
# 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 |
|-----------|------|---|---|---|---|---|---|---|-------|----|----|----|------|
<!-- one row per component with numeric counts, then Totals row -->
---
## [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:
- ✅
## KmsPluginProviderwith first line> **[New]** Component added in this release. - ✅
### FIND-01: Missing Auth Checkwith first line> **[Existing]** - ❌
## KmsPluginProvider [New](breaks#kmspluginprovideranchor) - ❌
### 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 unidentifiedRemoved— component was removed
<!-- SIMPLIFIED DISPLAY TAGS: Only 5 tags for display in markdown body text. [Existing] = still_present, modified, mitigated (threat still exists) [Fixed] = fixed (fully remediated) [Partial] = partially_mitigated (code changed but vulnerability remains in reduced form) [New] = new_code, new_in_modified, previously_unidentified (new to this report) [Removed] = removed_with_component (component deleted) JSON change_status keeps the detailed values for programmatic use. -->
⛔ 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 toLocal Process AccessorHost/OS Access - ZERO findings may be in
## Tier 1→ downgrade to T2/T3 - ZERO CVSS vectors may use
AV:Nunless component hasReachability = External
For ALL classifications:
- Each finding's prerequisite MUST be ≥ its component's
Min Prerequisitefrom 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 AccessandHost Accessare 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 |
<!-- ⛔ POST-STEP CHECK: After writing all finding annotations: 1. Every finding body starts with one of: [Existing], [Fixed], [Partial], [New], [Removed] 2. Tags are in body text as blockquote (> [Tag]), NOT in the ### heading 3. No old-style tags: [STILL PRESENT], [NEW CODE], [NEW IN MODIFIED], [PREVIOUSLY UNIDENTIFIED], [PARTIALLY MITIGATED], [REMOVED WITH COMPONENT] 4. JSON change_status uses the detailed values (still_present, new_code, etc.) for programmatic comparison -->
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:
{
"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):
## 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:
| 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:
<!-- Section 1: Header + Comparison Cards -->
<div class="header">
<div class="report-badge">INCREMENTAL THREAT MODEL COMPARISON</div>
<h1>{{repo_name}}</h1>
</div>
<div class="comparison-cards">
<div class="compare-card baseline">
<div class="card-label">BASELINE</div>
<div class="card-hash">{{baseline_sha}}</div>
<div class="card-date">{{baseline_commit_date from git log}}</div>
<div class="risk-badge">{{old_risk_rating}}</div>
</div>
<div class="compare-arrow">→</div>
<div class="compare-card target">
<div class="card-label">TARGET</div>
<div class="card-hash">{{target_sha}}</div>
<div class="card-date">{{target_commit_date from git log}}</div>
<div class="risk-badge">{{new_risk_rating}}</div>
</div>
<div class="compare-card trend">
<div class="card-label">TREND</div>
<div class="trend-direction">{{Improving|Worsening|Stable}}</div>
<div class="trend-duration">{{N months}}</div>
</div>
</div>
<!-- Section 2: Metrics Bar (5 boxes — NO Time Between, use Code Changes) -->
<div class="metrics-bar">
Components: {{old_count}} → {{new_count}} (±N)
Trust Boundaries: {{old_boundaries}} → {{new_boundaries}} (±N)
Threats: {{old_count}} → {{new_count}} (±N)
Findings: {{old_count}} → {{new_count}} (±N)
Code Changes: {{COMMIT_COUNT}} commits, {{PR_COUNT}} PRs
</div>
<!-- Section 3: Status Summary Cards (colored cards — primary visualization) -->
<div class="status-cards">
<!-- Green card: Fixed (count + list of fixed items) -->
<!-- Red card: New (code + modified) (count + list of new items) -->
<!-- Amber card: Previously Unidentified (count + list) -->
<!-- Gray card: Still Present (count) -->
</div>
<!-- Section 4: Component Status Grid -->
<table class="component-grid">
<!-- Row per component: ID | Type | Status (color-coded) | Source Files -->
</table>
<!-- Section 5: Threat/Finding Status Breakdown -->
<div class="status-breakdown">
<!-- Grouped by status: Fixed items, New items, etc. -->
<!-- Each item: ID | Title | Component | Status -->
</div>
<!-- Section 6: STRIDE Heatmap with Deltas -->
<!-- ⛔ MANDATORY: Heatmap MUST have 13 columns including T1/T2/T3 after a divider -->
<table class="stride-heatmap">
<thead>
<tr>
<th>Component</th>
<th>S</th><th>T</th><th>R</th><th>I</th><th>D</th><th>E</th><th>A</th>
<th>Total</th>
<th class="divider"></th>
<th>T1</th><th>T2</th><th>T3</th>
</tr>
</thead>
<tbody>
<!-- Row per component. Each STRIDE cell: value (▲+N or ▼-N delta from baseline) -->
<!-- The divider column is a thin visual separator between STRIDE totals and tier breakdown -->
</tbody>
</table>
<!-- Section 7: Needs Verification -->
<div class="needs-verification">
<!-- Items where analysis disagrees with old report -->
</div>
<!-- Section 8: Footer -->
<div class="footer">
Model: {{model}} | Duration: {{duration}}
Baseline: {{baseline_folder}} at {{baseline_sha}}
Generated: {{timestamp}}
</div>Styling rules:
- Self-contained: ALL CSS in inline
<style>block. No CDN links. - Color conventions: green (#28a745) = fixed, red (#dc3545) = new vulnerability, amber (#fd7e14) = previously unidentified, gray (#6c757d) = still present, blue (#2171b5) = modified
- Print-friendly: include
@media printstyles - Use the same CSS color conventions defined above for visual consistency
---
Phase 5: Verification
5a. Standard Verification
Run the standard verification-checklist.md (Phases 0–9) against the new report. The incremental report must pass ALL standard quality checks since it is a standalone report. Delegate to a sub-agent with the output folder absolute path so it can read the report files.
5b. Incremental Verification
After standard verification passes, run the incremental-specific checks from experiment-history/mode-c-verification-suite.md (Phases 1–9, 33 checks). These verify:
- Structural continuity (every old item accounted for)
- Code-verified status accuracy (e.g., "fixed" actually verified against code diff)
- Previously-unidentified classification (verified against baseline worktree)
- DFD consistency (old nodes present, new nodes distinguished)
- Standalone quality (no dangling references to old report)
- Comparison summary accuracy (counts match inventory)
- Needs Verification completeness
- Edge cases (merges, splits, rewrites)
- Metrics/JSON integrity
5c. Correction Workflow
1. Collect all PASS/FAIL results 2. For each FAIL → apply the check's "Fail remediation" action 3. Re-run failed checks to confirm they pass 4. After 2 correction attempts, escalate remaining failures to Needs Verification 5. Record end time and generate execution summary
---
⛔ Rules Specific to Incremental Analysis
These rules supplement (not replace) the 34 mandatory rules from orchestrator.md:
Rule I1: Old Report Assessment Judgments Are Preserved
When the new analysis would assign a different TMT category, component type, tier, or threat relevance than the old report → preserve the old report's value. Log the disagreement in Needs Verification with:
- Old value
- New analysis's proposed value
- 1-2 sentence reasoning
- What the user should check
Exception: Factual corrections (file paths, git metadata, arithmetic) are corrected silently and noted in Report Metadata.
Rule I2: No Silent Overrides
The report body uses the OLD value for assessment judgments. Disagreements go to Needs Verification. The user must explicitly confirm any reclassification.
Rule I3: Previously-Unidentified Must Be Verified
Every previously_unidentified classification MUST include evidence from the baseline worktree. The analyst must actually read the old code at the cited file/line and confirm the vulnerability pattern existed. No guessing based on "it's probably been there."
Rule I4: Fixed Must Be Code-Verified
Every fixed classification MUST cite the specific code change that addressed the vulnerability. Generic statements like "the team fixed this" are not acceptable — show the diff.
Rule I5: new_in_modified Requires Change Attribution
Every new_in_modified finding MUST identify the specific code change that introduced the vulnerability. Cite the diff hunk, new function, new config value, or new dependency that created the issue.
Rule I6: Do Not Delete Baseline Worktree
The baseline worktree may be reused by future incremental analyses. Do NOT run git worktree remove on it. The worktree path is recorded in Report Metadata for reference.
Rule I7: Change Status Consistency
A component's change_status must be consistent with its threats' and findings' statuses:
unchangedcomponent → its threats should bestill_present(orpreviously_unidentifiedfor newly discovered threats in unchanged code)removedcomponent → ALL its threats/findings must beremoved_with_componentmodifiedcomponent → at least one threat should bemodified,fixed, ornew_in_modifiednewcomponent → ALL its threats must benew_code
Rule I8: Carry Forward, Don't Copy
"Carry forward" means regenerating a threat/finding entry that says the same thing — NOT literally copy-pasting old report text. The regenerated entry should:
- Use the same ID
- Reference current file paths (even if unchanged)
- Be phrased in present tense about the current code
- Include the
[STILL PRESENT]annotation
---
Summary: Phase-by-Phase Checklist
| Phase | Action | Success Criteria |
|---|---|---|
| 0 | Setup, validate inputs, worktree | All inputs exist, worktree accessible |
| 1 | Load old inventory skeleton | All arrays populated, metrics match |
| 2 | Per-component change detection | Every component has a change_status |
| 3 | Scan for new components | New components identified, missed components flagged |
| 4 | Generate all report files | 8-9 files written to output folder |
| 5 | Verification (standard + incremental) | All checks pass or escalated to Needs Verification |
Skeleton: 0.1-architecture.md
⛔ Copy the template content below VERBATIM (excluding the outer code fence). Replace `[FILL]` placeholders. Do NOT add/rename/reorder sections.
⛔ Key Components table columns are EXACTLY: `Component | Type | Description`. DO NOT rename to `Role`, `Change`, `Function`.
⛔ Technology Stack table columns are EXACTLY: `Layer | Technologies` (2 columns). DO NOT add `Version` column or rename `Layer` to `Category`.
⛔ Security Infrastructure Inventory and Repository Structure sections are MANDATORY — do NOT omit them.
---
````markdown
Architecture Overview
System Purpose
[FILL-PROSE: 2-4 sentences — what is this system, what problem does it solve, who are the users]
Key Components
| Component | Type | Description |
|---|
[REPEAT: one row per component] | [FILL: PascalCase name] | [FILL: Process / Data Store / External Service / External Interactor] | [FILL: one-line description] | [END-REPEAT]
<!-- ⛔ POST-TABLE CHECK: Verify Key Components: 1. Every component has PascalCase name (not kebab-case or snake_case) 2. Type is one of: Process / Data Store / External Service / External Interactor 3. Row count matches the number of nodes in the Component Diagram below If ANY check fails → FIX NOW. -->
Component Diagram
[FILL: Architecture diagram using service/external/datastore styles — NOT DFD circles]Top Scenarios
[REPEAT: 3-5 scenarios. First 3 MUST include sequence diagrams.]
Scenario [FILL: N]: [FILL: Title]
[FILL-PROSE: 2-3 sentence description]
sequenceDiagram
[FILL: participants, messages, alt/opt blocks][END-REPEAT]
<!-- ⛔ POST-SECTION CHECK: Verify Top Scenarios: 1. At least 3 scenarios listed 2. First 3 scenarios MUST have sequenceDiagram blocks 3. Each sequence diagram has participant lines and message arrows If ANY check fails → FIX NOW. -->
Technology Stack
| Layer | Technologies |
|---|---|
| Languages | [FILL] |
| Frameworks | [FILL] |
| Data Stores | [FILL] |
| Infrastructure | [FILL] |
| Security | [FILL] |
<!-- ⛔ POST-TABLE CHECK: Verify Technology Stack has all 5 rows filled. If Security row is empty, list security-relevant libraries/frameworks found in the code. -->
Deployment Model
[FILL-PROSE: deployment description — ports, protocols, bind addresses, network exposure, topology (single machine / cluster / multi-tier)]
Deployment Classification: [FILL: one of LOCALHOST_DESKTOP | LOCALHOST_SERVICE | AIRGAPPED | K8S_SERVICE | NETWORK_SERVICE]
<!-- ⛔ DEPLOYMENT CLASSIFICATION RULES: LOCALHOST_DESKTOP — Single-process console/GUI app, no network listeners (or localhost-only), single-user workstation. T1 FORBIDDEN. LOCALHOST_SERVICE — Daemon/service binding to 127.0.0.1 only. T1 FORBIDDEN. AIRGAPPED — No internet connectivity. T1 forbidden for network-originated attacks. K8S_SERVICE — Kubernetes Deployment/StatefulSet with ClusterIP or LoadBalancer. T1 allowed. NETWORK_SERVICE — Public API, cloud endpoint, internet-facing. T1 allowed. This classification is BINDING on all subsequent prerequisite and tier assignments. -->
Component Exposure Table
| Component | Listens On | Auth Required | Reachability | Min Prerequisite | Derived Tier |
|---|
[REPEAT: one row per component from Key Components table] | [FILL: component name] | [FILL: port/address or "N/A — no listener"] | [FILL: Yes (mechanism) / No] | [FILL: one of: External / Internal Only / Localhost Only / No Listener] | [FILL: one of closed enum — see rules below] | [FILL: T1 / T2 / T3] | [END-REPEAT]
<!-- ⛔ EXPOSURE TABLE RULES: 1. Every component from Key Components MUST have a row. 2. "Listens On" = the actual bind address from code (e.g., "127.0.0.1:8080", "0.0.0.0:443", "N/A — no listener"). 3. "Reachability" MUST be one of these 4 values (closed enum):
External— reachable from public internet or untrusted networkInternal Only— reachable only within a private network (K8s cluster, VNet, etc.)Localhost Only— binds to 127.0.0.1 or named pipe, same-host onlyNo Listener— does not accept inbound connections (outbound-only, console I/O, library)
4. "Min Prerequisite" MUST be one of these values (closed enum):
None— only valid when Reachability = External AND Auth Required = NoAuthenticated User— Reachability = External AND Auth Required = YesInternal Network— Reachability = Internal Only AND Auth Required = NoPrivileged User— requires admin/operator roleLocal Process Access— Reachability = Localhost Only (same-host process can connect)Host/OS Access— Reachability = No Listener (requires filesystem, console, or debug access)Admin Credentials— requires admin credentials + host accessPhysical Access— requires physical presence
⛔ FORBIDDEN values: Application Access, Host Access (ambiguous — use Local Process Access or Host/OS Access instead) 5. "Derived Tier" is mechanically determined from Min Prerequisite:
None→ T1Authenticated User,Privileged User,Internal Network,Local Process Access→ T2Host/OS Access,Admin Credentials,Physical Access,{Component} Compromise, or anyA + B→ T3
6. No threat or finding for this component may have a LOWER prerequisite than Min Prerequisite. 7. No threat or finding for this component may have a HIGHER tier (lower number) than Derived Tier. 8. This table is the SINGLE SOURCE OF TRUTH for prerequisite floors and tier ceilings. STRIDE and findings MUST respect it. -->
Security Infrastructure Inventory
| Component | Security Role | Configuration | Notes |
|---|
[REPEAT: one row per security-relevant component found in code] | [FILL] | [FILL] | [FILL] | [FILL] | [END-REPEAT]
Repository Structure
| Directory | Purpose |
|---|
[REPEAT: one row per key directory] | [FILL: path/] | [FILL] | [END-REPEAT] ````
Skeleton: 0-assessment.md
⛔ Copy the template content below VERBATIM (excluding the outer code fence). Replace `[FILL]` placeholders. Do NOT add/rename/reorder sections.
[FILL]= single value |[FILL-PROSE]= paragraphs |[REPEAT]...[END-REPEAT]= N copies |[CONDITIONAL]...[END-CONDITIONAL]= include if condition met
---
# Security Assessment
---
## Report Files
| File | Description |
|------|-------------|
| [0-assessment.md](0-assessment.md) | This document — executive summary, risk rating, action plan, metadata |
| [0.1-architecture.md](0.1-architecture.md) | Architecture overview, components, scenarios, tech stack |
| [1-threatmodel.md](1-threatmodel.md) | Threat model DFD diagram with element, flow, and boundary tables |
| [1.1-threatmodel.mmd](1.1-threatmodel.mmd) | Pure Mermaid DFD source file |
| [2-stride-analysis.md](2-stride-analysis.md) | Full STRIDE-A analysis for all components |
| [3-findings.md](3-findings.md) | Prioritized security findings with remediation |
[CONDITIONAL: Include if 1.2-threatmodel-summary.mmd was generated]
| [1.2-threatmodel-summary.mmd](1.2-threatmodel-summary.mmd) | Summary DFD for large systems |
[END-CONDITIONAL]
[CONDITIONAL: Include for incremental analysis]
| [incremental-comparison.html](incremental-comparison.html) | Visual comparison report |
[END-CONDITIONAL]
<!-- ⛔ POST-TABLE CHECK: Verify Report Files:
1. `0-assessment.md` is the FIRST row (not 0.1-architecture.md)
2. All generated files are listed
3. Conditional rows (1.2-threatmodel-summary.mmd, incremental-comparison.html) only if those files exist
If ANY check fails → FIX NOW. -->
---
## Executive Summary
[FILL-PROSE: 2-3 paragraph summary of the system and its security posture]
[FILL: "The analysis covers [N] system elements across [M] trust boundaries."]
### Risk Rating: [FILL: Critical / Elevated / Moderate / Low]
[FILL-PROSE: risk rating justification paragraph]
> **Note on threat counts:** This analysis identified [FILL: N] threats across [FILL: M] components. This count reflects comprehensive STRIDE-A coverage, not systemic insecurity. Of these, **[FILL: T1 count] are directly exploitable** without prerequisites (Tier 1). The remaining [FILL: T2+T3 count] represent conditional risks and defense-in-depth considerations.
<!-- ⛔ POST-SECTION CHECK: Verify Executive Summary:
1. Risk Rating heading has NO emojis: `### Risk Rating: Elevated` not `### Risk Rating: 🟠 Elevated`
2. Note on threat counts blockquote is present
3. Element count and boundary count match actual counts from 1-threatmodel.md
If ANY check fails → FIX NOW. -->
---
## Action Summary
| Tier | Description | Threats | Findings | Priority |
|------|-------------|---------|----------|----------|
| [Tier 1](3-findings.md#tier-1--direct-exposure-no-prerequisites) | Directly exploitable | [FILL] | [FILL] | 🔴 Critical Risk |
| [Tier 2](3-findings.md#tier-2--conditional-risk-authenticated--single-prerequisite) | Requires authenticated access | [FILL] | [FILL] | 🟠 Elevated Risk |
| [Tier 3](3-findings.md#tier-3--defense-in-depth-prior-compromise--host-access) | Requires prior compromise | [FILL] | [FILL] | 🟡 Moderate Risk |
| **Total** | | **[FILL]** | **[FILL]** | |
<!-- ⛔ POST-TABLE CHECK: Verify Action Summary:
1. EXACTLY 4 data rows: Tier 1, Tier 2, Tier 3, Total — NO 'Mitigated', 'Platform', or 'Fixed' rows
2. Priority column is FIXED: Tier 1=🔴 Critical Risk, Tier 2=🟠 Elevated Risk, Tier 3=🟡 Moderate Risk — never changed based on counts
3. Threats column sums match 2-stride-analysis.md Totals row
4. Findings column sums match 3-findings.md FIND- heading count
5. Tier 1/2/3 cells are hyperlinks to 3-findings.md tier headings — verify anchors resolve
If ANY check fails → FIX NOW before continuing. -->
### Priority by Tier and CVSS Score (Top 10)
| Finding | Tier | CVSS Score | SDL Severity | Title |
|---------|------|------------|-------------|-------|
[REPEAT: top 10 findings only, sorted by Tier (T1 first, then T2, then T3), then by CVSS score descending within each tier]
| [FIND-XX](3-findings.md#find-xx-title-slug) | T[FILL] | [FILL] | [FILL] | [FILL] |
[END-REPEAT]
<!-- ⛔ POST-TABLE CHECK: Verify Priority by Tier and CVSS Score:
1. Maximum 10 rows (top 10 findings only, not all findings)
2. Sort order: ALL Tier 1 findings first (by CVSS desc), then Tier 2 (by CVSS desc), then Tier 3 (by CVSS desc)
3. Every Finding cell is a hyperlink: [FIND-XX](3-findings.md#find-xx-title-slug)
4. Verify each hyperlink anchor resolves: compute the anchor from the ACTUAL heading text in 3-findings.md (lowercase, spaces→hyphens, strip special chars). The link must match whatever the heading is.
5. CVSS scores match the actual finding's CVSS value in 3-findings.md
If ANY check fails → FIX NOW. -->
### Quick Wins
<!-- Quick Wins Finding column: each Finding cell MUST be a hyperlink to 3-findings.md, same format as Priority table:
[FIND-XX](3-findings.md#find-xx-title-slug)
Compute the anchor from the ACTUAL heading text in 3-findings.md. -->
| Finding | Title | Why Quick |
|---------|-------|-----------|
[REPEAT]
| [FIND-XX](3-findings.md#find-xx-title-slug) | [FILL] | [FILL] |
[END-REPEAT]
---
[CONDITIONAL: Include ONLY for incremental analysis]
## Change Summary
### Component Changes
| Status | Count | Components |
|--------|-------|------------|
| Unchanged | [FILL] | [FILL] |
| Modified | [FILL] | [FILL] |
| New | [FILL] | [FILL] |
| Removed | [FILL] | [FILL] |
### Threat Status
| Status | Count |
|--------|-------|
| Existing | [FILL] |
| Fixed | [FILL] |
| New | [FILL] |
| Removed | [FILL] |
### Finding Status
| Status | Count |
|--------|-------|
| Existing | [FILL] |
| Fixed | [FILL] |
| Partial | [FILL] |
| New | [FILL] |
| Removed | [FILL] |
### Risk Direction
[FILL: Improving / Worsening / Stable] — [FILL-PROSE: 1-2 sentence justification]
---
## Previously Unidentified Issues
[FILL-PROSE: or "No previously unidentified issues found."]
| Finding | Title | Component | Evidence |
|---------|-------|-----------|----------|
[REPEAT]
| [FILL] | [FILL] | [FILL] | [FILL] |
[END-REPEAT]
[END-CONDITIONAL]
---
## Analysis Context & Assumptions
### Analysis Scope
| Constraint | Description |
|------------|-------------|
| Scope | [FILL] |
| Excluded | [FILL] |
| Focus Areas | [FILL] |
### Infrastructure Context
| Category | Discovered from Codebase | Findings Affected |
|----------|--------------------------|-------------------|
[REPEAT]
| [FILL] | [FILL: include relative file links] | [FILL] |
[END-REPEAT]
### Needs Verification
| Item | Question | What to Check | Why Uncertain |
|------|----------|---------------|---------------|
[REPEAT]
| [FILL] | [FILL] | [FILL] | [FILL] |
[END-REPEAT]
### Finding Overrides
| Finding ID | Original Severity | Override | Justification | New Status |
|------------|-------------------|----------|---------------|------------|
| — | — | — | No overrides applied. Update this section after review. | — |
### Additional Notes
[FILL-PROSE: or "No additional notes."]
---
## References Consulted
### Security Standards
| Standard | URL | How Used |
|----------|-----|----------|
| Microsoft SDL Bug Bar | https://www.microsoft.com/en-us/msrc/sdlbugbar | Severity classification |
| OWASP Top 10:2025 | https://owasp.org/Top10/2025/ | Threat categorization |
| CVSS 4.0 | https://www.first.org/cvss/v4.0/specification-document | Risk scoring |
| CWE | https://cwe.mitre.org/ | Weakness classification |
| STRIDE | https://learn.microsoft.com/en-us/azure/security/develop/threat-modeling-tool-threats | Threat enumeration |
[REPEAT: additional standards if used]
| [FILL] | [FILL] | [FILL] |
[END-REPEAT]
### Component Documentation
| Component | Documentation URL | Relevant Section |
|-----------|------------------|------------------|
[REPEAT]
| [FILL] | [FILL] | [FILL] |
[END-REPEAT]
---
## Report Metadata
| Field | Value |
|-------|-------|
| Source Location | `[FILL]` |
| Git Repository | `[FILL]` |
| Git Branch | `[FILL]` |
| Git Commit | `[FILL: SHA from git rev-parse --short HEAD]` (`[FILL: date from git log -1 --format="%ai" — NOT today's date]`) |
| Model | `[FILL]` |
| Machine Name | `[FILL]` |
| Analysis Started | `[FILL]` |
| Analysis Completed | `[FILL]` |
| Duration | `[FILL]` |
| Output Folder | `[FILL]` |
| Prompt | `[FILL: the user's prompt text that triggered this analysis]` |
[CONDITIONAL: incremental]
| Baseline Report | `[FILL]` |
| Baseline Commit | `[FILL: SHA]` (`[FILL: commit date]`) |
| Target Commit | `[FILL: SHA]` (`[FILL: commit date]`) |
| Baseline Worktree | `[FILL]` |
| Analysis Mode | `Incremental` |
[END-CONDITIONAL]
<!-- ⛔ POST-TABLE CHECK: Verify Report Metadata:
1. ALL values wrapped in backticks: `value`
2. Git Commit, Baseline Commit, Target Commit each include date in parentheses
3. Duration field is present (not missing)
4. Model field states the actual model name
5. Analysis Started and Analysis Completed are real timestamps (not estimated from folder name)
If ANY check fails → FIX NOW. -->
---
## Classification Reference
<!-- SKELETON INSTRUCTION: Copy the table below verbatim. Do NOT modify values. Do NOT copy this HTML comment into the output. -->
| Classification | Values |
|---------------|--------|
| **Exploitability Tiers** | **T1** Direct Exposure (no prerequisites) · **T2** Conditional Risk (single prerequisite) · **T3** Defense-in-Depth (multiple prerequisites or infrastructure access) |
| **STRIDE + Abuse** | **S** Spoofing · **T** Tampering · **R** Repudiation · **I** Information Disclosure · **D** Denial of Service · **E** Elevation of Privilege · **A** Abuse (feature misuse) |
| **SDL Severity** | `Critical` · `Important` · `Moderate` · `Low` |
| **Remediation Effort** | `Low` · `Medium` · `High` |
| **Mitigation Type** | `Redesign` · `Standard Mitigation` · `Custom Mitigation` · `Existing Control` · `Accept Risk` · `Transfer Risk` |
| **Threat Status** | `Open` · `Mitigated` · `Platform` |
| **Incremental Tags** | `[Existing]` · `[Fixed]` · `[Partial]` · `[New]` · `[Removed]` (incremental reports only) |
| **CVSS** | CVSS 4.0 vector with `CVSS:4.0/` prefix |
| **CWE** | Hyperlinked CWE ID (e.g., [CWE-306](https://cwe.mitre.org/data/definitions/306.html)) |
| **OWASP** | OWASP Top 10:2025 mapping (e.g., A01:2025 – Broken Access Control) |Critical format rules baked into this skeleton:
0-assessment.mdis the FIRST row in Report Files (not0.1-architecture.md)## Analysis Context & Assumptionsuses&(never word "and")---horizontal rules between EVERY pair of##sections (minimum 6)### Quick Winsalways present (with fallback note if no low-effort findings)### Needs Verificationand### Finding Overridesalways present (even if empty with—)- References has TWO subsections with THREE-column tables (never flat 2-column)
- ALL metadata values wrapped in backticks
- ALL metadata fields present (Model, Analysis Started, Analysis Completed, Duration)
- Risk Rating heading has NO emojis
- Action Summary has EXACTLY 4 data rows: Tier 1, Tier 2, Tier 3, Total — NO "Mitigated" or "Platform" rows
- Git Commit rows include commit date in parentheses:
SHA(date)
Skeleton: 1.1-threatmodel.mmd
⛔ This is a raw Mermaid file — NO markdown wrapper. Line 1 MUST start with `%%{init:`.
The init block, classDefs, and linkStyle are FIXED — never change colors/strokes.
Diagram direction is ALWAYS `flowchart LR` — NEVER `flowchart TB`.
⛔ The template below is shown inside a code fence for readability only — do NOT include the fence in the output file.
---
%%{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
[CONDITIONAL: incremental mode — include BOTH lines below]
classDef newComponent fill:#d4edda,stroke:#28a745,stroke-width:3px,color:#000000
classDef removedComponent fill:#e9ecef,stroke:#6c757d,stroke-width:1px,stroke-dasharray:5,color:#6c757d
[END-CONDITIONAL]
[REPEAT: one line per external actor/interactor — outside all subgraphs]
[FILL: NodeID]["[FILL: Display Name]"]:::external
[END-REPEAT]
[REPEAT: one subgraph per trust boundary]
subgraph [FILL: BoundaryID]["[FILL: Boundary Display Name]"]
[REPEAT: processes and datastores inside this boundary]
[FILL: NodeID](("[FILL: Process Name]")):::process
[FILL: NodeID][("[FILL: DataStore Name]")]:::datastore
[END-REPEAT]
end
[END-REPEAT]
[REPEAT: one line per data flow — use <--> for bidirectional request-response]
[FILL: SourceID] <-->|"[FILL: DF##: description]"| [FILL: TargetID]
[END-REPEAT]
[REPEAT: one style line per trust boundary subgraph]
style [FILL: BoundaryID] fill:none,stroke:#e31a1c,stroke-width:3px,stroke-dasharray: 5 5
[END-REPEAT]
linkStyle default stroke:#666666,stroke-width:2pxNEVER change these fixed elements:
%%{init:themeVariables: onlybackground,primaryColor,lineColorflowchart LR— never TB- classDef colors: process=#6baed6/#2171b5, external=#fdae61/#d94701, datastore=#74c476/#238b45
- Incremental classDefs (when applicable): newComponent=#d4edda/#28a745 (light green), removedComponent=#e9ecef/#6c757d (gray dashed)
- New components MUST use
:::newComponent(NOT:::process). Removed components MUST use:::removedComponent. - Trust boundary style:
fill:none,stroke:#e31a1c,stroke-width:3px,stroke-dasharray: 5 5 - linkStyle:
stroke:#666666,stroke-width:2px
DFD shapes:
- Process:
(("Name"))(double parentheses = circle) - Data Store:
[("Name")](bracket-paren = cylinder) - External:
["Name"](brackets = rectangle) - All labels MUST be quoted in
"" - All subgraph IDs:
subgraph ID["Title"]
<!-- ⛔ POST-DFD GATE — IMMEDIATELY after creating this file: 1. Count element nodes: lines with (("...")), [("...")], ["..."] shapes 2. Count boundaries: lines with 'subgraph' 3. If elements > 15 OR boundaries > 4: → OPEN skeleton-summary-dfd.md and create 1.2-threatmodel-summary.mmd NOW → Do NOT proceed to 1-threatmodel.md until summary exists 4. If threshold NOT met → skip summary, proceed to 1-threatmodel.md This is the most frequently skipped step. The gate is MANDATORY. -->
Skeleton: 3-findings.md
⛔ Copy the template content below VERBATIM (excluding the outer code fence). Replace `[FILL]` placeholders. ALL 10 attribute rows are MANDATORY per finding. Organize by TIER, not by severity.
⛔ DO NOT abbreviate attribute names. Use EXACT names: `SDL Bugbar Severity` (not `Severity`), `Exploitation Prerequisites` (not `Prerequisites`), `Exploitability Tier` (not `Tier`), `Remediation Effort` (not `Effort`), `CVSS 4.0` (not `CVSS Score`).
⛔ DO NOT use bold inline headers (`Description:`). Use `#### Description` markdown h4 headings.
⛔ Tier section headings MUST be: `## Tier 1 — Direct Exposure (No Prerequisites)`, NOT `## Tier 1 Findings`.
---
# Security Findings
---
## Tier 1 — Direct Exposure (No Prerequisites)
[REPEAT: one finding block per Tier 1 finding, sorted by severity (Critical→Important→Moderate→Low) then CVSS descending]
### FIND-[FILL: NN]: [FILL: title]
| Attribute | Value |
|-----------|-------|
| SDL Bugbar Severity | [FILL: Critical / Important / Moderate / Low] |
| CVSS 4.0 | [FILL: N.N] (CVSS:4.0/[FILL: full vector starting with AV:]) |
| CWE | [CWE-[FILL: NNN]](https://cwe.mitre.org/data/definitions/[FILL: NNN].html): [FILL: weakness name] |
| OWASP | A[FILL: NN]:2025 – [FILL: category name] |
| Exploitation Prerequisites | [FILL: text or "None"] |
| Exploitability Tier | Tier [FILL: 1/2/3] — [FILL: tier description] |
| Remediation Effort | [FILL: Low / Medium / High] |
| Mitigation Type | [FILL: Redesign / Standard Mitigation / Custom Mitigation / Existing Control / Accept Risk / Transfer Risk] |
| Component | [FILL: component name] |
| Related Threats | [T[FILL: NN].[FILL: X]](2-stride-analysis.md#[FILL: component-anchor]), [T[FILL: NN].[FILL: X]](2-stride-analysis.md#[FILL: component-anchor]) |
<!-- ⛔ POST-FINDING CHECK: Verify this finding IMMEDIATELY:
1. ALL 10 attribute rows present (SDL Bugbar Severity through Related Threats)
2. Row names are EXACT: 'SDL Bugbar Severity' (not 'SDL Bugbar'), 'Exploitation Prerequisites' (not 'Prerequisites'), 'Exploitability Tier' (not 'Risk Tier'), 'Remediation Effort' (not 'Effort')
3. Related Threats are HYPERLINKS with `](2-stride-analysis.md#` — NOT plain text like 'T01.S, T02.T'
4. CVSS starts with `CVSS:4.0/` — NOT bare vector
5. CWE is a hyperlink to cwe.mitre.org — NOT plain text
6. OWASP uses `:2025` suffix — NOT `:2021`
If ANY check fails → FIX THIS FINDING NOW before writing the next one. -->
#### Description
[FILL-PROSE: technical description of the vulnerability]
#### Evidence
**Prerequisite basis:** [FILL: cite the specific code/config that determines this finding's prerequisite — e.g., "binds to 127.0.0.1 only (src/Server.cs:42)", "no auth middleware on /api routes (Startup.cs:18)", "console app with no network listener (Program.cs)". This MUST match the Component Exposure Table in 0.1-architecture.md.]
[FILL: specific file paths, line numbers, config keys, code snippets]
#### Remediation
[FILL: actionable remediation steps]
#### Verification
[FILL: how to verify the fix was applied]
<!-- ⛔ POST-SECTION CHECK: Verify this finding's sub-sections:
1. Exactly 4 sub-headings present: `#### Description`, `#### Evidence`, `#### Remediation`, `#### Verification`
2. Sub-headings use `####` level (NOT bold `**Description:**` inline text)
3. No extra sub-headings like `#### Impact`, `#### Recommendation`, `#### Mitigation`
4. Description has at least 2 sentences of technical detail
5. Evidence cites specific file paths or line numbers (not generic)
If ANY check fails → FIX NOW before moving to next finding. -->
[END-REPEAT]
[CONDITIONAL-EMPTY: If no Tier 1 findings, include this line instead of the REPEAT block]
*No Tier 1 findings identified for this repository.*
[END-CONDITIONAL-EMPTY]
---
## Tier 2 — Conditional Risk (Authenticated / Single Prerequisite)
[REPEAT: same finding block structure as Tier 1, sorted same way]
### FIND-[FILL: NN]: [FILL: title]
| Attribute | Value |
|-----------|-------|
| SDL Bugbar Severity | [FILL] |
| CVSS 4.0 | [FILL] (CVSS:4.0/[FILL]) |
| CWE | [CWE-[FILL]](https://cwe.mitre.org/data/definitions/[FILL].html): [FILL] |
| OWASP | A[FILL]:2025 – [FILL] |
| Exploitation Prerequisites | [FILL] |
| Exploitability Tier | Tier [FILL] — [FILL] |
| Remediation Effort | [FILL] |
| Mitigation Type | [FILL] |
| Component | [FILL] |
| Related Threats | [FILL] |
#### Description
[FILL-PROSE]
#### Evidence
**Prerequisite basis:** [FILL: cite the specific code/config that determines this finding's prerequisite — must match the Component Exposure Table in 0.1-architecture.md]
[FILL]
#### Remediation
[FILL]
#### Verification
[FILL]
[END-REPEAT]
[CONDITIONAL-EMPTY: If no Tier 2 findings, include this line instead of the REPEAT block]
*No Tier 2 findings identified for this repository.*
[END-CONDITIONAL-EMPTY]
---
## Tier 3 — Defense-in-Depth (Prior Compromise / Host Access)
[REPEAT: same finding block structure]
### FIND-[FILL: NN]: [FILL: title]
| Attribute | Value |
|-----------|-------|
| SDL Bugbar Severity | [FILL] |
| CVSS 4.0 | [FILL] (CVSS:4.0/[FILL]) |
| CWE | [CWE-[FILL]](https://cwe.mitre.org/data/definitions/[FILL].html): [FILL] |
| OWASP | A[FILL]:2025 – [FILL] |
| Exploitation Prerequisites | [FILL] |
| Exploitability Tier | Tier [FILL] — [FILL] |
| Remediation Effort | [FILL] |
| Mitigation Type | [FILL] |
| Component | [FILL] |
| Related Threats | [FILL] |
#### Description
[FILL-PROSE]
#### Evidence
**Prerequisite basis:** [FILL: cite the specific code/config that determines this finding's prerequisite — must match the Component Exposure Table in 0.1-architecture.md]
[FILL]
#### Remediation
[FILL]
#### Verification
[FILL]
[END-REPEAT]
[CONDITIONAL-EMPTY: If no Tier 3 findings, include this line instead of the REPEAT block]
*No Tier 3 findings identified for this repository.*
[END-CONDITIONAL-EMPTY]At the END of 3-findings.md, append the Threat Coverage Verification table:
---
## Threat Coverage Verification
| Threat ID | Finding ID | Status |
|-----------|------------|--------|
[REPEAT: one row per threat from ALL components in 2-stride-analysis.md]
| [FILL: T##.X] | [FILL: FIND-## or —] | [FILL: ✅ Covered (FIND-XX) / ✅ Mitigated (FIND-XX) / 🔄 Mitigated by Platform] |
[END-REPEAT]
<!-- ⛔ POST-TABLE CHECK: Verify Threat Coverage Verification:
1. Status column uses ONLY these 3 values with emoji prefixes:
- `✅ Covered (FIND-XX)` — vulnerability needs remediation
- `✅ Mitigated (FIND-XX)` — team built a control (documented in finding)
- `🔄 Mitigated by Platform` — external platform handles it
2. Do NOT use plain text like "Finding", "Mitigated", "Covered" without the emoji
3. Do NOT use "Needs Review", "Accepted Risk", or "N/A"
4. Column headers are EXACTLY: `Threat ID | Finding ID | Status` (NOT `Threat | Finding | Status`)
5. Every threat from 2-stride-analysis.md appears in this table (no missing threats)
If ANY check fails → FIX NOW. -->Fixed rules baked into this skeleton:
- Finding ID:
FIND-prefix (neverF-,F01,Finding) - Attribute names:
SDL Bugbar Severity,Exploitation Prerequisites,Exploitability Tier,Remediation Effort(exact — not abbreviated) - CVSS: starts with
CVSS:4.0/(never bare vector) - CWE: hyperlinked (never plain text)
- OWASP:
:2025suffix (never:2021) - Related Threats: individual hyperlinks (never plain text)
- Sub-sections:
#### Description,#### Evidence,#### Remediation,#### Verification - Organized by TIER — no
## Critical Findingsor## Mitigatedsections - Exactly 3 tier sections (all mandatory, even if empty with "No Tier N findings identified.")
Skeleton: incremental-comparison.html
⛔ Self-contained HTML — ALL CSS inline. No CDN links. Follow this exact 8-section structure.
---
The HTML report has exactly 8 sections in this order. Each section MUST be present.
Section 1: Header + Comparison Cards
<div class="header">
<div class="report-badge">INCREMENTAL THREAT MODEL COMPARISON</div>
<h1>[FILL: repo name]</h1>
</div>
<div class="comparison-cards">
<div class="compare-card baseline">
<div class="card-label">BASELINE</div>
<div class="card-hash">[FILL: baseline SHA]</div>
<div class="card-date">[FILL: baseline commit date from git log]</div>
<div class="risk-badge [FILL: old-class]">[FILL: old rating]</div>
</div>
<div class="compare-arrow">→</div>
<div class="compare-card target">
<div class="card-label">TARGET</div>
<div class="card-hash">[FILL: target SHA]</div>
<div class="card-date">[FILL: target commit date from git log]</div>
<div class="risk-badge [FILL: new-class]">[FILL: new rating]</div>
</div>
<div class="compare-card trend">
<div class="card-label">TREND</div>
<div class="trend-direction [FILL: color]">[FILL: Improving / Worsening / Stable]</div>
<div class="trend-duration">[FILL: N months]</div>
</div>
</div><!-- SKELETON INSTRUCTION: Section 2 (Risk Shift) is merged into Section 1 above. The old separate risk-shift div is removed. The comparison-cards div replaces both the old subtitle + risk-shift + time-between box. -->
Section 2: Metrics Bar (5 boxes)
<div class="metrics-bar">
[FILL: Components: old → new (±N)]
[FILL: Trust Boundaries: old → new (±N)]
[FILL: Threats: old → new (±N)]
[FILL: Findings: old → new (±N)]
[FILL: Code Changes: N commits, M PRs — use git rev-list --count and git log --oneline --merges --grep="Merged PR"]
</div>MUST include Trust Boundaries as one of the 5 metrics. 5th box is Code Changes (NOT Time Between).
Section 3: Status Summary Cards (colored)
<div class="status-cards">
<!-- Green card --> Fixed: [FILL: count] [FILL: 1-sentence summary, NO IDs]
<!-- Red card --> New: [FILL: count] [FILL: 1-sentence summary, NO IDs]
<!-- Amber card --> Previously Unidentified: [FILL: count] [FILL: 1-sentence summary, NO IDs]
<!-- Gray card --> Still Present: [FILL: count] [FILL: 1-sentence summary, NO IDs]
</div><!-- SKELETON INSTRUCTION: Status cards show COUNT + a short human-readable sentence ONLY. DO NOT include threat IDs (T06.S, T02.E), finding IDs (FIND-14), or component names. Good: "1 credential handling vulnerability remediated" Good: "4 new components with 21 new threats identified" Good: "No new threats or findings introduced" Bad: "T06.S: DefaultAzureCredential → ManagedIdentityCredential" Bad: "ConfigurationOrchestrator — 5 threats (T16.), LLMService — 6 threats (T17.)" The detailed item-by-item breakdown with IDs belongs in Section 5 (Threat/Finding Status Breakdown). --> Status info appears ONLY here — NOT also in the metrics bar.
Section 4: Component Status Grid
<table class="component-grid">
<tr><th>Component</th><th>Type</th><th>Status</th><th>Source Files</th></tr>
[REPEAT: one row per component with color-coded status badge]
<tr><td>[FILL]</td><td>[FILL]</td><td><span class="badge-[FILL: status]">[FILL]</span></td><td>[FILL]</td></tr>
[END-REPEAT]
</table>Section 5: Threat/Finding Status Breakdown
<div class="status-breakdown">
[FILL: Grouped by status — Fixed items, New items, etc.]
[REPEAT: Each item: ID | Title | Component | Status]
[END-REPEAT]
</div>Section 6: STRIDE Heatmap with Deltas
<table class="stride-heatmap">
<thead>
<tr>
<th>Component</th>
<th>S</th><th>T</th><th>R</th><th>I</th><th>D</th><th>E</th><th>A</th>
<th>Total</th>
<th class="divider"></th>
<th>T1</th><th>T2</th><th>T3</th>
</tr>
</thead>
<tbody>
[REPEAT: one row per component]
<tr>
<td>[FILL: component]</td>
<td>[FILL: S value] [FILL: delta indicator ▲/▼]</td>
... [same for T, R, I, D, E, A, Total] ...
<td class="divider"></td>
<td>[FILL: T1]</td><td>[FILL: T2]</td><td>[FILL: T3]</td>
</tr>
[END-REPEAT]
</tbody>
</table>MUST have 13 columns: Component + S + T + R + I + D + E + A + Total + divider + T1 + T2 + T3
Section 7: Needs Verification
<div class="needs-verification">
[REPEAT: items where analysis disagrees with old report]
[FILL: item description]
[END-REPEAT]
</div>Section 8: Footer
<div class="footer">
Model: [FILL] | Duration: [FILL]
Baseline: [FILL: folder] at [FILL: SHA]
Generated: [FILL: timestamp]
</div>---
Fixed CSS variables (use in `<style>` block):
--red: #dc3545; /* new vulnerability */
--green: #28a745; /* fixed/improved */
--amber: #fd7e14; /* previously unidentified */
--gray: #6c757d; /* still present */
--accent: #2171b5; /* modified/info */Fixed rules:
- ALL CSS in inline
<style>block — no external stylesheets - Include
@media printstyles - Heatmap MUST have T1/T2/T3 columns after divider
- Metrics bar MUST include Trust Boundaries
- Status data in cards ONLY — not duplicated in metrics bar
- HTML threat/finding totals MUST match markdown STRIDE summary totals
Skeleton: threat-inventory.json
⛔ Use EXACT field names shown below. Common errors: `display_name` (wrong→`display`), `category` (wrong→`stride_category`), `name` (wrong→`title`).
⛔ The template below is shown inside a code fence for readability only — do NOT include the fence in the output file. The `.json` file must start with `{` on line 1.
---
{
"schema_version": "[FILL: 1.0 for standalone, 1.1 for incremental]",
"report_folder": "[FILL: threat-model-YYYYMMDD-HHmmss]",
"commit": "[FILL: short SHA]",
"commit_date": "[FILL: commit date UTC]",
"branch": "[FILL]",
"repository": "[FILL: remote URL]",
"analysis_timestamp": "[FILL: UTC timestamp]",
"model": "[FILL]",
"components": [
[REPEAT: sorted by id]
{
"id": "[FILL: PascalCase]",
"display": "[FILL: display name — NOT display_name]",
"type": "[FILL: process / external_service / data_store / external_interactor]",
"tmt_type": "[FILL: SE.P.TMCore.* / SE.EI.TMCore.* / SE.DS.TMCore.* from tmt-element-taxonomy.md]",
"boundary": "[FILL: boundary ID]",
"boundary_kind": "[FILL: MachineBoundary / NetworkBoundary / ClusterBoundary / ProcessBoundary / PrivilegeBoundary / SandboxBoundary]",
"aliases": [],
"source_files": ["[FILL: relative paths]"],
"source_directories": ["[FILL: relative dirs]"],
"fingerprint": {
"component_type": "[FILL: process / external_service / data_store / external_interactor]",
"boundary_kind": "[FILL: MachineBoundary / NetworkBoundary / ClusterBoundary / ProcessBoundary / PrivilegeBoundary / SandboxBoundary]",
"source_files": ["[FILL: relative paths]"],
"source_directories": ["[FILL: relative dirs — MUST NOT be empty for process-type]"],
"class_names": ["[FILL]"],
"namespace": "[FILL]",
"config_keys": [],
"api_routes": [],
"dependencies": [],
"inbound_from": ["[FILL: component IDs that send data TO this component]"],
"outbound_to": ["[FILL: component IDs this component sends data TO]"],
"protocols": ["[FILL: gRPC / HTTPS / SQL / etc.]"]
},
"sidecars": ["[FILL: co-located sidecar names, or empty array]"]
}
[END-REPEAT]
],
"boundaries": [
[REPEAT: sorted by id]
{
"id": "[FILL: PascalCase boundary ID]",
"display": "[FILL]",
"kind": "[FILL: MachineBoundary / NetworkBoundary / ClusterBoundary / ProcessBoundary / PrivilegeBoundary / SandboxBoundary]",
"aliases": [],
"contains": ["[FILL: component IDs]"],
"contains_fingerprint": "[FILL: sorted pipe-delimited component IDs]"
}
[END-REPEAT]
],
"flows": [
[REPEAT: sorted by id]
{
"id": "[FILL: DF_Source_to_Target]",
"from": "[FILL: component ID]",
"to": "[FILL: component ID]",
"protocol": "[FILL]",
"description": "[FILL: 1 sentence max]"
}
[END-REPEAT]
],
"threats": [
[REPEAT: sorted by id then identity_key.component_id]
{
"id": "[FILL: T##.X]",
"title": "[FILL: short title — REQUIRED]",
"description": "[FILL: 1 sentence — REQUIRED]",
"stride_category": "[FILL: S/T/R/I/D/E/A — SINGLE LETTER, NOT full word]",
"tier": [FILL: 1/2/3],
"prerequisites": "[FILL]",
"status": "[FILL: Open/Mitigated/Platform]",
"mitigation": "[FILL: 1 sentence or empty]",
"identity_key": {
"component_id": "[FILL: PascalCase — MUST be inside identity_key, NOT top-level]",
"data_flow_id": "[FILL: DF_Source_to_Target]",
"stride_category": "[FILL: S/T/R/I/D/E/A]",
"attack_surface": "[FILL: brief description of the attack surface]"
}
}
[END-REPEAT]
],
"findings": [
[REPEAT: sorted by id then identity_key.component_id]
{
"id": "[FILL: FIND-##]",
"title": "[FILL]",
"severity": "[FILL: Critical/Important/Moderate/Low]",
"cvss_score": [FILL: N.N],
"cvss_vector": "[FILL: CVSS:4.0/AV:...]",
"cwe": "[FILL: CWE-###]",
"owasp": "[FILL: A##:2025]",
"tier": [FILL: 1/2/3],
"effort": "[FILL: Low/Medium/High]",
"related_threats": ["[FILL: T##.X]"],
"evidence_files": ["[FILL: relative paths]"],
"component": "[FILL: display name]",
"identity_key": {
"component_id": "[FILL: PascalCase]",
"vulnerability": "[FILL: CWE-###]",
"attack_surface": "[FILL: file:key or endpoint]"
}
}
[END-REPEAT]
],
"metrics": {
"total_components": [FILL],
"total_boundaries": [FILL],
"total_flows": [FILL],
"total_threats": [FILL],
"total_findings": [FILL],
"threats_by_tier": { "T1": [FILL], "T2": [FILL], "T3": [FILL] },
"findings_by_tier": { "T1": [FILL], "T2": [FILL], "T3": [FILL] },
"threats_by_stride": { "S": [FILL], "T": [FILL], "R": [FILL], "I": [FILL], "D": [FILL], "E": [FILL], "A": [FILL] },
"findings_by_severity": { "Critical": [FILL], "Important": [FILL], "Moderate": [FILL], "Low": [FILL] }
}
}MANDATORY field name compliance:
"display"— NOT"display_name","name""stride_category"— NOT"category"— SINGLE LETTER (S/T/R/I/D/E/A)"title"AND"description"— both required on every threatidentity_key.component_id— component link INSIDE identity_key, NOT top-level- Sort all arrays deterministically before writing
Skeleton: 1-threatmodel.md
⛔ Copy the template content below VERBATIM (excluding the outer code fence). Replace `[FILL]` placeholders. Diagram in `.md` and `.mmd` must be IDENTICAL.
⛔ Data Flow Table columns: `ID | Source | Target | Protocol | Description`. DO NOT rename `Target` to `Destination`. DO NOT reorder columns.
⛔ Trust Boundary Table columns: `Boundary | Description | Contains` (3 columns). DO NOT add a `Name` column or rename `Contains` to `Components Inside`.
---
````markdown
Threat Model
Data Flow Diagram
[FILL: Copy EXACT content from 1.1-threatmodel.mmd]Element Table
| Element | Type | TMT Category | Description | Trust Boundary |
|---|
[CONDITIONAL: For K8s apps with sidecars, add a Co-located Sidecars column after Trust Boundary] [REPEAT: one row per element] | [FILL] | [FILL: Process / External Interactor / Data Store] | [FILL: SE.P.TMCore. / SE.EI.TMCore. / SE.DS.TMCore.*] | [FILL] | [FILL] | [END-REPEAT]
Data Flow Table
| ID | Source | Target | Protocol | Description |
|---|
[REPEAT: one row per data flow] | [FILL: DF##] | [FILL] | [FILL] | [FILL] | [FILL] | [END-REPEAT]
Trust Boundary Table
| Boundary | Description | Contains |
|---|
[REPEAT: one row per trust boundary] | [FILL] | [FILL] | [FILL: comma-separated component list] | [END-REPEAT]
[CONDITIONAL: Include ONLY if summary diagram was generated (elements > 15 OR boundaries > 4)]
Summary View
[FILL: Copy EXACT content from 1.2-threatmodel-summary.mmd]Summary to Detailed Mapping
| Summary Element | Contains | Summary Flows | Maps to Detailed Flows |
|---|
[REPEAT] | [FILL] | [FILL] | [FILL: SDF##] | [FILL: DF##, DF##] | [END-REPEAT]
[END-CONDITIONAL] ````
Fixed rules:
- Use
DF01,DF02for detailed flows;SDF01,SDF02for summary flows - Element Type: exactly
Process,External Interactor, orData Store - TMT Category: must be a specific ID from tmt-element-taxonomy.md (e.g.,
SE.P.TMCore.WebSvc)
Related skills
How it compares
Pick threat-model-analyst over generic security linter skills when analyzing cloud-native stacks where platform defaults often satisfy controls.
FAQ
What modes does threat-model-analyst support?
Single full STRIDE-A analysis and incremental mode that updates a prior threat-model report with change tracking and HTML comparison.
When does incremental mode activate?
When the user asks to update or refresh a threat model and a baseline threat-model folder with threat-inventory.json exists.
What outputs does a single analysis produce?
Architecture overview, threat model, STRIDE-A analysis, prioritized findings, and executive assessment files following reference skeletons.
Is Threat Model Analyst safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.