{# Defensive defaults — placed BEFORE the render-mode gate. #} {% set portfolio_summary = portfolio_summary | default({}) %} {% if portfolio_summary is string or portfolio_summary is not mapping %}{% set portfolio_summary = {} %}{% endif %} {% set findings_df = findings_df | default([]) %} {% set project_summary_df = project_summary_df | default([]) %} {% set gate_funnel = gate_funnel | default({}) %} {% if gate_funnel is string or gate_funnel is not mapping %}{% set gate_funnel = {} %}{% endif %} {% set vex_recommendations = vex_recommendations | default([]) %} {% if vex_recommendations is mapping or vex_recommendations is string or vex_recommendations is not iterable %}{% set vex_recommendations = [] %}{% endif %} {% import "_console_macros.html" as fs with context %} {% if render_mode != 'fragment' %} {% if domain %}{% endif %} Configuration Analysis Triage — {{ recipe_name }} {% include "_echarts_ready.html" %} {% include "_design_system.html" %} {% include "_console_shell.html" %} {% endif %} {# Recipe-local styles — OUTSIDE the render-mode gate so fragment_extractor auto-scopes under .fs-section-configuration-analysis-triage. Hex literals migrated to design tokens; SEMANTIC class names from the pre-migration template (.cat-CRYPTO_MATERIAL, .band-CRITICAL, .vex-rec-IN_TRIAGE, .pk-yes, etc.) preserved verbatim. NEW layout/structural classes added by the migration use a `.cat-section-*` prefix to disambiguate from the semantic enum classes. #} {% if render_mode != 'fragment' %} {% endif %} {# Build topbar meta items. #} {% set cat_meta = [{"label": "Domain", "value": domain or "—"}] %} {% if portfolio_summary is mapping %} {% set _ = cat_meta.append({"label": "Total Findings", "value": (portfolio_summary.get('total', 0)) | string}) %} {% endif %} {% if metadata is mapping and metadata.get('start_date') and metadata.get('end_date') %} {% set _ = cat_meta.append({"label": "Period", "value": metadata.start_date ~ " → " ~ metadata.end_date}) %} {% endif %} {% if metadata is mapping and metadata.get('transformed_count') is not none %} {% set _ = cat_meta.append({"label": "Records", "value": metadata.transformed_count | string}) %} {% endif %} {% set _ = cat_meta.append({"label": "Generated", "value": generated_at or "—"}) %} {% if render_mode != 'fragment' %} {{ fs.topbar( crumbs=["Finite State", "Configuration Analysis Triage"], meta=cat_meta, controls=[], ) }} {% endif %}

Configuration Analysis Triage Report

{# Subtitle preserves the pre-migration 3-branch conditional (configuration_analysis_triage.html:135-143) so folder/ project scope appears under the title. #}

{% if folder_path %} Folder: {{ folder_path }}{% if is_single_project and single_project_name %} / Project: {{ single_project_name }}{% endif %} — Config, Secrets & Crypto Triage {% elif is_single_project and single_project_name %} Project: {{ single_project_name }} — Config, Secrets & Crypto Triage {% else %} Config, Secrets & Crypto Triage {% endif %}

{# 6 KPI cells via fs.kpi_cell. #}
{{ fs.kpi_cell( "Critical", portfolio_summary.get('CRITICAL', 0) if portfolio_summary is mapping else 0, dot=("critical" if (portfolio_summary is mapping and portfolio_summary.get('CRITICAL', 0) > 0) else None), ) }} {{ fs.kpi_cell( "High", portfolio_summary.get('HIGH', 0) if portfolio_summary is mapping else 0, dot=("high" if (portfolio_summary is mapping and portfolio_summary.get('HIGH', 0) > 0) else None), ) }} {{ fs.kpi_cell( "Medium", portfolio_summary.get('MEDIUM', 0) if portfolio_summary is mapping else 0, dot=("medium" if (portfolio_summary is mapping and portfolio_summary.get('MEDIUM', 0) > 0) else None), ) }} {{ fs.kpi_cell( "Low", portfolio_summary.get('LOW', 0) if portfolio_summary is mapping else 0, dot=("low" if (portfolio_summary is mapping and portfolio_summary.get('LOW', 0) > 0) else None), ) }} {{ fs.kpi_cell("Info", portfolio_summary.get('INFO', 0) if portfolio_summary is mapping else 0) }} {{ fs.kpi_cell("Total", portfolio_summary.get('total', 0) if portfolio_summary is mapping else 0) }}
{# Two-column chart row: band distribution + gate funnel. #}

Priority Band Distribution

{% if render_mode == 'fragment' and not fragment_scripts_enabled %}
Chart unavailable in fragment view
{% else %}
{% endif %}

Gate Classification Funnel

{% if render_mode == 'fragment' and not fragment_scripts_enabled %}
Chart unavailable in fragment view
{% else %}
{% endif %}
{# Category breakdown chart (full-width). #}

Category Breakdown

{% if render_mode == 'fragment' and not fragment_scripts_enabled %}
Chart unavailable in fragment view
{% else %}
{% endif %}
{# Project summary chart — conditional on multi-project data. #} {% if project_summary_df is iterable and project_summary_df is not string and project_summary_df is not mapping and (project_summary_df | length) > 1 %}

Project Risk Summary

{% if render_mode == 'fragment' and not fragment_scripts_enabled %}
Chart unavailable in fragment view
{% else %}
{% endif %}
{% endif %} {# Scoring methodology callout — DYNAMIC content driven by scoring_config.gates and scoring_config.weights (NOT static prose). Mirrors configuration_analysis_triage.html:225-255 so gate thresholds and weight values stay accurate when the recipe YAML parameters change. #}

Scoring Methodology

This report uses a tiered-gates scoring model that prioritizes configuration, credential, and cryptographic material findings based on their risk characteristics.

{% if scoring_config and scoring_config.gates %} {% for gate in scoring_config.gates %}

{{ gate.name | replace('_', ' ') | title }} — {{ gate.band }} (score={{ gate.score }})

{% endfor %} {% endif %} {% if scoring_config and scoring_config.weights %}

Additive Scoring (Remaining Findings)

Bands: HIGH ≥ {{ scoring_config.weights.band_high_threshold|default(0) }}, MEDIUM ≥ {{ scoring_config.weights.band_medium_threshold|default(0) }}, LOW ≥ {{ scoring_config.weights.band_low_threshold|default(0) }}, INFO < {{ scoring_config.weights.band_low_threshold|default(0) }}

{% endif %}
{# Project Risk Summary table — preserves pre-migration (configuration_analysis_triage.html:257-303) conditional (`length > 0 and not is_single_project`; the single-project case is conveyed by the subtitle), entity-link wrapping on project_name, band-badge spans for CRITICAL/HIGH/MEDIUM severity columns, and toggleRows('tbl-project-summary') pagination. #} {% if project_summary_df is defined and project_summary_df is iterable and project_summary_df is not string and project_summary_df is not mapping and (project_summary_df | length) > 0 and not is_single_project %}

Project Risk Summary

{% for row in project_summary_df %} {% endfor %} {% if render_mode != 'fragment' and project_summary_df|length > 10 %} {% endif %}
Project CRITICALHIGHMEDIUM LOWINFOTotal
{% if domain and row.project_id is defined and row.project_id %} {{ row.project_name }} {% else %} {{ row.project_name }} {% endif %} {% if row.CRITICAL|default(0) > 0 %}{{ row.CRITICAL }}{% else %}0{% endif %} {% if row.HIGH|default(0) > 0 %}{{ row.HIGH }}{% else %}0{% endif %} {% if row.MEDIUM|default(0) > 0 %}{{ row.MEDIUM }}{% else %}0{% endif %} {{ row.LOW|default(0) }} {{ row.INFO|default(0) }} {{ row.total|default(0) }}
{% endif %} {# Findings detail table — only render when there's data; pre-migration matched. Preserves pre-migration .hidden-row pagination (first 10 visible; toggleRows('tbl-findings-detail') reveals the rest), entity-link deep-linking, semantic .cat-badge + .band-badge + .pk-yes/no + .vex-badge classes, and the :100 row cap from the pre-migration template. #} {% if findings_df is defined and findings_df is iterable and findings_df is not string and findings_df is not mapping and (findings_df | length) > 0 %}

Findings Detail (Top 100 by Priority)

{% if findings_df is iterable and findings_df is not string and findings_df is not mapping %} {% for row in findings_df[:100] %} {% endfor %} {% if render_mode != 'fragment' and findings_df[:100]|length > 10 %} {% endif %} {% endif %}
Title Category Severity Band Score Gate Material Type Private Key File Path Component Project VEX Status
{% if domain and row.project_id and row.version_id and row.id %} {{ row.title|default('—') }} {% else %} {{ row.title|default('—') }} {% endif %} {% set cat = row.category|default('') %} {% if cat %} {{ cat }} {% else %}—{% endif %} {{ row.severity|default('—') }} {{ row.priority_band|default('INFO') }} {{ row.triage_score|default(0) }} {{ row.gate_assignment if row.gate_assignment and row.gate_assignment != 'NONE' else '—' }} {{ row.detail_material_type|default('—') }} {% if row.detail_private_key is defined and row.detail_private_key is not none and row.detail_private_key|string not in ('', 'None', 'nan') %} {% if row.detail_private_key == true or row.detail_private_key|string|lower == 'true' %} 🔑 Yes {% else %} No {% endif %} {% else %}—{% endif %} {% if row.detail_file_path|default('') %} {{ row.detail_file_path }} {% else %}—{% endif %} {% if domain and row.project_id and row.version_id and row.component_id %} {{ row.component_name|default('—') }}{% if row.component_version|default('') %} {{ row.component_version }}{% endif %} {% else %} {{ row.component_name|default('—') }}{% if row.component_version|default('') %} {{ row.component_version }}{% endif %} {% endif %} {% if domain and row.project_id %} {{ row.project_name|default('—') }} {% else %} {{ row.project_name|default('—') }} {% endif %} {% if row.status is defined and row.status and row.status|string not in ('', 'nan', 'None') %} {{ row.status }} {% else %} — {% endif %}
{% endif %} {# VEX recommendations table — semantic .cat-badge / .band-badge / .vex-rec-badge classes preserved from pre-migration. The transform emits ONLY recommended_vex_status of "IN_TRIAGE" or "NOT_AFFECTED" (configuration_analysis_triage.py:565-575) — do NOT fabricate other statuses. finding_common_id falls back to finding_id (pre-migration safety fallback at line 419). #} {% if vex_recommendations and (vex_recommendations | length) > 0 %}

VEX Recommendations

Recommended VEX triage actions for un-triaged findings. Apply with fs-report apply-vex.

{% for rec in vex_recommendations %} {% if rec is mapping %} {% endif %} {% endfor %} {% if render_mode != 'fragment' and vex_recommendations|length > 10 %} {% endif %}
Finding ID Category Severity Score Band Gate Recommended Status Reason
{{ rec.finding_common_id|default(rec.finding_id)|default('—') }} {% set rcat = rec.category|default('') %} {% if rcat %} {{ rcat }} {% else %}—{% endif %} {{ rec.severity|default('—') }} {{ rec.triage_score|default(0) }} {% set rband = rec.priority_band|default('INFO') %} {{ rband }} {{ rec.gate if rec.gate and rec.gate != 'NONE' else '—' }} {% set rstatus = rec.recommended_vex_status|default('') %} {% if rstatus %} {{ rstatus }} {% else %}—{% endif %} {{ rec.reason|default('—') }}
{% endif %}
{% if render_mode != 'fragment' %} {{ fs.status_bar(items=[ "Recipe: Configuration Analysis Triage", "Findings: " ~ ((portfolio_summary.get('total', 0) if portfolio_summary is mapping else 0) | string), "Critical: " ~ ((portfolio_summary.get('CRITICAL', 0) if portfolio_summary is mapping else 0) | string), generated_at | default(''), ], version='fs-report ' ~ (fs_report_version | default('dev'))) }} {% endif %} {% if render_mode != 'fragment' or fragment_scripts_enabled %} {% include '_action_buttons.html' %} {% endif %}