* Add threat-model-analyst skill: STRIDE-A threat modeling for repositories Add a comprehensive threat model analysis skill that performs security audits using STRIDE-A (STRIDE + Abuse) threat modeling, Zero Trust principles, and defense-in-depth analysis. Supports two modes: - Single analysis: full STRIDE-A threat model producing architecture overviews, DFD diagrams, prioritized findings, and executive assessments - Incremental analysis: security posture diff between baseline report and current code, producing standalone reports with embedded comparison Includes bundled reference assets: - Orchestrator workflows (full and incremental) - Analysis principles and verification checklists - Output format specifications and skeleton templates - DFD diagram conventions and TMT element taxonomy * Address PR review comments from Copilot reviewer - Fix SKILL.md description: use single-quoted scalar, rename mode (2) to 'Incremental analysis' with accurate description - Replace 'Compare Mode (Deprecated)' sections with 'Comparing Commits or Reports' redirect (no deprecated language for first release) - Fix skeleton-findings.md: move Tier 1 table rows under header, add CONDITIONAL-EMPTY block after END-REPEAT (matching Tier 2/3 structure) - Fix skeleton-threatmodel.md and skeleton-architecture.md: use 4-backtick outer fences to avoid nested fence conflicts with inner mermaid fences - Fix skeleton-incremental-html.md: correct section count from 9 to 8 - Fix output-formats.md: change status 'open' to 'Open' in JSON example, move stride_category warning outside JSON fence as blockquote - Fix incremental-orchestrator.md: replace stale compare-output-formats.md reference with inline color conventions - Regenerate docs/README.skills.md with updated description * Address second round of Copilot review comments - Fix diagram-conventions.md: bidirectional flow notation now uses <--> matching orchestrator.md and DFD templates - Fix tmt-element-taxonomy.md: normalize SE.DF.SSH/LDAP/LDAPS to use SE.DF.TMCore.* prefix consistent with all other data flow IDs - Fix output-formats.md: correct TMT category example from SQLDatabase to SQL matching taxonomy, fix component type from 'datastore' to 'data_store' matching canonical enum, remove DaprSidecar from inbound_from per no-standalone-sidecar rule - Fix 5 skeleton files: clarify VERBATIM instruction to 'copy the template content below (excluding the outer code fence)' to prevent agents from wrapping output in markdown fences - Genericize product-specific names in examples: replace edgerag with myapp, BitNetManager with TaskProcessor, AzureLocalMCP with MyApp.Core, AzureLocalInfra with OnPremInfra, MilvusVectorDB with VectorDB * Address third round of Copilot review comments - Fix diagram-conventions.md: second bidirectional two-arrow pattern in Quick Reference section now uses <--> - Fix incremental-orchestrator.md: renumber HTML sections 5-9 to 4-8 matching skeleton-incremental-html.md 8-section structure - Fix output-formats.md: add incremental-comparison.html to File List as conditional output for incremental mode - Fix skeleton-inventory.md: add tmt_type, sidecars, and boundary_kind fields to match output-formats.md JSON schema example
34 KiB
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:
- The user's request involves updating, re-running, or refreshing a threat model
- A prior
threat-model-*folder exists in the repository with a validthreat-inventory.json - 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
-
Record start time:
Get-Date -Format "yyyy-MM-dd HH:mm:ss" -AsUTCStore as
START_TIME. -
Gather git info:
git remote get-url origin git branch --show-current git rev-parse --short HEAD hostname -
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):
Store as
git rev-list --count {baseline_sha}..{target_sha} git log --oneline --merges --grep="Merged PR" {baseline_sha}..{target_sha} | wc -lCOMMIT_COUNTandPR_COUNT.
- Confirm baseline folder exists:
-
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_WORKTREEfor old-code verification in later phases. -
Create output folder:
threat-model-{YYYYMMDD-HHmmss}/
Phase 1: Load Old Report Skeleton
Read the baseline threat-inventory.json and extract the structural skeleton:
From threat-inventory.json, load:
- components[] → all component IDs, types, boundaries, source_files, fingerprints
- flows[] → all flow IDs, from/to, protocols
- boundaries[] → all boundary IDs, contains lists
- threats[] → all threat IDs, component mappings, stride categories, tiers
- findings[] → all finding IDs, titles, severities, CWEs, component mappings
- metrics → totals for validation
Store as the "inherited inventory" — the structural foundation.
Do NOT read the full prose from the old report's markdown files yet. Only load structured data. Read old report prose on-demand when:
- Verifying if a specific code pattern was previously analyzed
- Resolving ambiguity about a component's role or classification
- Historical context needed for a finding status decision
Phase 2: Per-Component Change Detection
For each component in the inherited inventory, determine its change status:
For EACH component in inherited inventory:
1. Check source_files existence at target commit:
git ls-tree {target_sha} -- {each source_file}
2. If ALL source files missing:
→ change_status = "removed"
→ Mark all linked threats as "removed_with_component"
→ Mark all linked findings as "removed_with_component"
3. If source files exist, check for changes:
git diff --stat {baseline_sha} {target_sha} -- {source_files}
If NO changes → change_status = "unchanged"
If changes exist, check if security-relevant:
Read the diff: git diff {baseline_sha} {target_sha} -- {source_files}
Look for changes in:
- Auth/credential patterns (tokens, passwords, certificates)
- Network/API surface (new endpoints, changed listeners, port bindings)
- Input validation (sanitization, parsing, deserialization)
- Command execution patterns (shell exec, process spawn)
- Config values (TLS settings, CORS, security headers)
- Dependencies (new packages, version changes)
If security-relevant → change_status = "modified"
If cosmetic only (whitespace, comments, logging, docs) → change_status = "unchanged"
4. If files moved or renamed:
git log --follow --diff-filter=R {baseline_sha}..{target_sha} -- {source_files}
→ change_status = "restructured"
→ Update source_file references to new paths
Record the classification for every component — this drives all downstream decisions.
Phase 3: Scan for New Components
1. Enumerate source directories/files at {target_sha} that are NOT referenced
by any existing component's source_files or source_directories.
Focus on: new top-level directories, new *Service.cs/*Agent.cs/*Server.cs classes,
new Helm deployments, new API controllers.
2. Apply the same component discovery rules from orchestrator.md:
- Class-anchored naming (PascalCase from actual class names)
- Component eligibility criteria (crosses trust boundary or handles security data)
- Same naming procedure (primary class → script → config → directory → technology)
3. For each candidate new component:
- Verify it didn't exist at baseline: git ls-tree {baseline_sha} -- {path}
- If it existed at baseline → this is a "missed component" from the old analysis
→ Add to Needs Verification section with note: "Component existed at baseline
but was not in the previous analysis. May indicate an analysis gap."
- If genuinely new (files didn't exist at baseline):
→ change_status = "new"
→ Assign a new component ID following the same PascalCase naming rules
→ Full STRIDE analysis will be performed in Phase 4
Phase 4: Generate Report Files
Now generate all report files. Read the relevant skill files before starting:
orchestrator.md— mandatory rules 1–34 apply to all report 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.mdfirst — 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.mdMUST contain:**Deployment Classification:** \[VALUE]`line (e.g.,K8S_SERVICE,LOCALHOST_DESKTOP`)### Component Exposure Tablewith 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.mdandskeletons/skeleton-summary-dfd.mdfirst -
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 → create1.2-threatmodel-summary.mmdusingskeleton-summary-dfd.mdNOW. Do NOT proceed to Step 4c until the decision is made.
4c. 1-threatmodel.md
- Read
skeletons/skeleton-threatmodel.mdfirst — use table structure - Element table: all old elements + new elements, with an added
Statuscolumn- Values:
Unchanged,Modified,New,Removed,Restructured
- Values:
- 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.mdfirst — use Summary table and per-component structure
⛔ CRITICAL REMINDERS FOR INCREMENTAL STRIDE (these rules from orchestrator.md apply identically here):
- 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.
- The
## Summarytable 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]
- 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.
- ⛔ PREREQUISITE FLOOR CHECK (per threat): Before assigning a prerequisite to any threat, look up the component's
Min PrerequisiteandDerived Tierin 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 fromanalysis-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 AccessandHost Accessare 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
⛔ POST-STEP CHECK: After writing the Change column for ALL threats, verify:
- Every threat row has exactly one of: Existing, Fixed, New, Removed
- No old-style tags: Still Present, New (Code), New (Modified), Previously Unidentified
- 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 = ExternalFor 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
#### Evidencesection 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 |
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:
- Identify the finding's component and evidence files
- Read the same files at the baseline commit:
cat {BASELINE_WORKTREE}/{file_path} - If the vulnerability pattern exists in the old code →
previously_unidentified - If the vulnerability pattern does NOT exist in the old code →
new_in_modified
4f. threat-inventory.json
- Read
skeletons/skeleton-inventory.mdfirst — 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.mdfirst — use section order and table structures
Standard assessment sections (all 7 mandatory) plus incremental-specific sections:
Standard sections (same as single analysis):
- Report Files
- Executive Summary (with
> **Note on threat counts:**blockquote) - Action Summary (with
### Quick Wins) - Analysis Context & Assumptions (with
### Needs Verificationand### Finding Overrides) - References Consulted
- Report Metadata
- 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.mdfirst — 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
- Collect all PASS/FAIL results
- For each FAIL → apply the check's "Fail remediation" action
- Re-run failed checks to confirm they pass
- After 2 correction attempts, escalate remaining failures to Needs Verification
- 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 |