{# Extended Threat Modeling dashboard: coverage, a portfolio-total STRIDE x asset-class matrix plus one per project, threat disposition (tested vs. untested mitigations), accepted threats reconciled with recorded human owners, models needing rework, recurring threat archetypes, model-depth mix, post-build misses, and falsified assumptions. See web/threat_modeling_dashboard.py for what's real vs. approximated. #} {% from "_macros.html" import info_tip %} {% macro stride_table(matrix) %}
{% endmacro %} Program dashboardShare of in-scope systems that have at least one successful threat model.
In scope means every project that has an assessment. To raise it, model the projects listed under Models needing rework.
{% endcall %} {{ tm_dash.coverage_pct }}% Coverage — {{ tm_dash.coverage_reviewed }} / {{ tm_dash.coverage_in_scope }} in-scope systemsThreats a model left as a Gap (no mitigation) where that model is more than {{ past_due_sla_days }} days old.
Threats have no individual due dates, so the model's age stands in for one. Close the gap, or record a risk acceptance with secfoo accept create.
Threats marked Mitigated with no test reference in the threat register: the control is claimed, not proven.
Add a test, pen-test or scan reference in the next model to move them to Mitigated + tested.
{% endcall %} {{ tm_dash.mitigated_untested }} Threats mitigated but untestedThe middle value of how many threats each project's latest model found.
A very low number usually means a shallow model rather than a safe system. Compare with Model depth mix.
{% endcall %} {{ tm_dash.median_threats_per_model if tm_dash.median_threats_per_model is not none else "—" }} Median threats per modelCounts the threats in each project's latest model by STRIDE category (columns) and by the kind of element they target (rows).
Red cells (none found) are the ones to read: that combination was never examined, which is not the same as safe. Choose a project to see which system a gap belongs to.
{% endcall %}Empty cells are the signal — a blank column means that category was never exercised, not that it's safe from it.
By project — a blank cell here is a real gap on that system; the total above can't say which project it came from.
{% if tm_dash.stride_by_project | length > 1 %} {% endif %}What happened to every threat in each project's latest model:
Mitigated + tested (control proven), Mitigated, untested (claimed only), Accepted (risk taken on), Transferred (to a vendor or insurer), Open (a gap), and Past due (a gap in a model older than {{ past_due_sla_days }} days).
{% endcall %}Every threat a model marked Accepted, matched with the person who signed it off.
A threat with no owner shown has not been signed off by anyone yet. Record the owner with secfoo accept create.
This card shows the newest ones. The full list is on its own page.
{% endcall %}
The newest threats marked Accepted, reconciled with who signed off. secfoo accept create is how "not recorded" turns into a real name.
| Threat | Project | Accepted by |
|---|---|---|
| {{ item.threat }} | {{ item.project }} | {% if item.accepted_by %}{{ item.accepted_by }} {% else %}Not recorded{% endif %} |
No accepted threats yet.
{% endif %}In-scope projects whose threat model is missing, stale (latest successful model is over a year old), or whose trust boundaries changed between the last two models.
Sorted by open findings, as a stand-in for how critical the system is.
{% endcall %} {% if tm_dash.needs_rework %}{{ tm_dash.needs_rework | length }}{% endif %} {% if tm_dash.needs_rework %} {% else %}The most common kinds of threat across every successful threat model, including older ones.
A pattern that keeps coming back is best fixed once, at platform level (a shared control or library), rather than project by project.
{% endcall %} {% if tm_dash.recurring_patterns %}{{ tm_dash.recurring_patterns | length }}{% endif %} {% if tm_dash.recurring_patterns %} {% else %}How thorough each project's latest threat model was, as the model declares about itself.
Full covers the whole system. Feature-level covers one feature or new data flow. Lightweight is a fast checklist pass.
Lightweight models miss more. Critical systems should have a Full model; a mostly-lightweight portfolio means coverage looks better than it is.
{% endcall %}How deep each project's latest model went{% if depth_total %} ({{ depth_total }} model{{ "s" if depth_total != 1 else "" }}){% endif %}.
{% if depth_total %} {% endif %}More lightweight than full models: re-run critical systems at --depth standard.
Threats discovered after release (an incident, a pen test, a later scan) that an earlier model should have caught.
Recorded by hand with secfoo miss, since a model can't know what it missed. A rising count means models are too shallow. This card shows the newest ones; the full list is on its own page.
None recorded yet — see secfoo miss.
Assumptions a model relied on (for example, "only internal traffic reaches this service") that it then checked against the code and found false.
Each one marks a place where the design and the real system disagree. This card shows the newest ones; the full list is on its own page.
{% endcall %}{{ tm_dash.falsified_assumptions | length }} checked against the code and found false.
{% if tm_dash.falsified_preview %} {% if tm_dash.falsified_assumptions | length > tm_dash.falsified_preview | length %} View all {{ tm_dash.falsified_assumptions | length }} → {% endif %} {% else %}No falsified assumptions recorded yet.
{% endif %}