{% import "_console_macros.html" as fs with context %} {% if render_mode != 'fragment' %} {% if domain %}{% endif %} Triage Prioritization - Finite State Report {% include "_design_system.html" %} {% include "_console_shell.html" %} {% include "_echarts_ready.html" %} {% endif %} {# Recipe-local styles render in BOTH standalone and fragment modes so fragment_extractor (_STYLE_RE) collects them, scopes under .fs-section-triage-prioritization, and re-emits at the top of the fragment. #} {% if render_mode != 'fragment' %} {% endif %} {# Findings-table slice, hoisted ahead of the sidebar so the band/quick-filter counts describe exactly what a filter click can show (round-1 review, 3/3 finding). Fragment consumers without scripts (forge MCP, email digests) keep the full top-100; only the compound bundle trades detail for page count. #} {% set _is_bundle = render_mode == 'fragment' and fragment_scripts_enabled %} {% set _findings_limit = 25 if _is_bundle else 100 %} {% set _findings_slice = (findings_df[:_findings_limit]) if findings_df is defined else [] %} {# ============================================================ TOP BAR — brand + breadcrumbs + run metadata + theme toggle (fsConnectionBadge lives inside the topbar via _console_macros) ============================================================ #} {% set _scope_label = (single_project_name if is_single_project and single_project_name else 'portfolio') %} {% set _crumbs = [] %} {% if folder_path %}{% set _ = _crumbs.append(folder_path) %}{% endif %} {% set _ = _crumbs.append(_scope_label) %} {% set _ = _crumbs.append('triage-prioritization') %} {% set _records = (metadata.transformed_count if metadata and metadata.transformed_count else None) %} {% set _meta_items = [] %} {% if metadata and metadata.start_date and metadata.end_date %} {% set _ = _meta_items.append({'label': 'period', 'value': metadata.start_date ~ ' → ' ~ metadata.end_date}) %} {% endif %} {% set _ = _meta_items.append({'label': 'generated', 'value': generated_at}) %} {% if render_mode != 'fragment' %} {{ fs.topbar(crumbs=_crumbs, meta=_meta_items, pill=single_version_name, controls=[]) }} {% endif %} {# ============================================================ LAYOUT SHELL — sidebar + main ============================================================ #}
{# -------------------------- SIDEBAR -------------------------- #} {# --------------------------- MAIN ---------------------------- #}
{# Page head: title + record count badge + scope subtitle. The section-title marker sits on the WRAPPING ``
`` (not the inner H1) so compound assembly's ``suppress_section_title=True`` removes the whole chrome strip — title + record badge + subtitle line + controls — in one go. With the marker on just the H1, the subtitle (Folder/Project/Version scope) and the Methodology link would leak under the SECTION 0N divider in compound bundles, duplicating chrome that the assembler already supplies. Single-recipe fragment consumers (forge MCP) omit ``suppress_section_title`` so this hero stays intact for them. (Round-1 multi-review M1-2.) #}

Triage Prioritization{% if _records %}{{ _records }} records{% endif %}

{% if folder_path %}Folder: {{ folder_path }}{% if is_single_project and single_project_name %} · Project: {{ single_project_name }}{% endif %}{% if single_version_name %} · Version: {{ single_version_name }}{% endif %} {% elif is_single_project and single_project_name %}Project: {{ single_project_name }}{% if single_version_name %} · Version: {{ single_version_name }}{% endif %} {% elif single_version_name %}Version: {{ single_version_name }} {% else %}Risk-Based Vulnerability Triage & Prioritization{% endif %}
{# AI depth advisory banner #} {% if portfolio_summary and portfolio_summary.ai_depth_banner %}
⚠ AI ran at summary depth. Component-level structured verdicts ran and have been applied to VEX recommendations where applicable. Finding-level deep analysis did not run, so portfolio narrative below may flag individual findings as likely false positives without a corresponding VEX update. Rerun with --ai-depth full or run False Positive Analysis --ai to produce per-finding applicability verdicts.
{% endif %} {# -------------------- KPI STRIP -------------------- #} {% if portfolio_summary %} {% set _total = portfolio_summary.total|default(0) %} {% set _kev_n = (findings_df | selectattr('in_kev') | list | length) if findings_df is defined else 0 %} {% set _expl_n = (findings_df | selectattr('has_exploit') | list | length) if findings_df is defined else 0 %} {% set _reach_n = (findings_df | selectattr('reachability_label', 'equalto', 'REACHABLE') | list | length) if findings_df is defined else 0 %}
{{ fs.kpi_cell(label='Critical', value=portfolio_summary.CRITICAL|default(0), delta='reachable + exploit/KEV', dot='critical') }} {{ fs.kpi_cell(label='High', value=portfolio_summary.HIGH|default(0), delta='network · EPSS ≥ 90%', dot='high') }} {{ fs.kpi_cell(label='Medium', value=portfolio_summary.MEDIUM|default(0), delta='additive score', dot='medium') }} {{ fs.kpi_cell(label='Low', value=portfolio_summary.LOW|default(0), delta='additive score', dot='low') }} {{ fs.kpi_cell(label='Info', value=portfolio_summary.INFO|default(0), delta='additive score', dot='info') }} {{ fs.kpi_cell(label='KEV / Exploit', value=_kev_n ~ ' / ' ~ _expl_n, delta='CISA + ExploitDB') }} {{ fs.kpi_cell(label='Reachable', value=_reach_n, delta='of ' ~ _total ~ ' findings') }}
{% endif %} {# -------------------- Distribution + Gate Funnel -------------------- #}
{{ fs.panel_head('Priority band distribution', 'doughnut') }}

Distribution of findings across risk priority bands.

{% if render_mode == 'fragment' and not fragment_scripts_enabled %}
Chart unavailable in fragment view
{% else %}
{% endif %}
{{ fs.panel_head('Factor drivers by band', 'avg points per finding') }}

One bar per priority band, normalized to the average factor contribution per finding in the band — so cross-band comparisons aren't biased by band size. Segments to the right of zero are positive drivers (reach, exploit, KEV, attack vector, EPSS, CVSS, gate bonus); segments to the left of zero are demoters (unreachable penalty, VEX demotion). Read it as: what's pushing the typical finding into this tier — and what's pulling it down? A CRITICAL bar dominated by reach + exploit means reachability and exploit maturity are the heavy levers; a LOW bar with a long left-of-zero unreachable segment means reachability demotion is the dominant force.

{% if render_mode == 'fragment' and not fragment_scripts_enabled %}
Chart unavailable in fragment view
{% else %}
{% endif %}
{# -------------------- CVSS vs priority band -------------------- #}
{{ fs.panel_head('CVSS severity vs priority band', 'stacked by band') }}

Each row is a CVSS tier. Segments inside each bar show how those findings break down across priority bands. Segments whose band doesn't match the row's CVSS tier are where CVSS alone would miscategorize risk.

{% if render_mode == 'fragment' and not fragment_scripts_enabled %}
Chart unavailable in fragment view
{% else %}
{% endif %}
{# -------------------- Top Components + Risk Factor Radar -------------------- #}
{{ fs.panel_head('Top 15 riskiest components', 'avg triage score, stacked by band') }} {% if render_mode == 'fragment' and not fragment_scripts_enabled %}
Chart unavailable in fragment view
{% else %}
{% endif %}
{{ fs.panel_head('Risk factor profiles', 'top projects') }} {% if render_mode == 'fragment' and not fragment_scripts_enabled %}
Chart unavailable in fragment view
{% else %}
{% endif %}
{# -------------------- Project Summary chart -------------------- #} {% if project_summary_df is defined and project_summary_df|length > 0 %}
{{ fs.panel_head('Project risk summary', 'sorted by CRITICAL count desc') }}

Per-project breakdown showing band counts.

{% if render_mode == 'fragment' and not fragment_scripts_enabled %}
Chart unavailable in fragment view
{% else %}
{% endif %}
{% endif %} {# -------------------- Methodology -------------------- #}
{{ fs.panel_head('Scoring methodology') }}

This report uses a tiered-gates scoring model that prioritizes findings based on real-world exploitability rather than theoretical CVSS scores alone.

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

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

  • {{ gate.description | replace(' AND ', ' AND ') | replace(' OR ', ' OR ') | safe }}
{% endfor %} {% endif %} {% if scoring_config and scoring_config.weights %}

Additive scoring (remaining findings)

  • Reachability: +{{ scoring_config.weights.reachable }} (reachable), {{ scoring_config.weights.unknown }} (inconclusive), {{ scoring_config.weights.unreachable }} (unreachable)
  • Exploit/KEV: +{{ scoring_config.weights.exploit }} (exploit), +{{ scoring_config.weights.kev_only }} (KEV only)
  • Attack vector: +{{ scoring_config.weights.vector_network }} (NETWORK), +{{ scoring_config.weights.vector_adjacent }} (ADJACENT), +{{ scoring_config.weights.vector_local }} (LOCAL), {{ scoring_config.weights.vector_physical }} (PHYSICAL)
  • EPSS: 0–{{ scoring_config.weights.epss_max }} points (scaled by percentile)
  • CVSS: 0–{{ scoring_config.weights.cvss_max }} points (scaled by score/10)

Bands: HIGH ≥ {{ scoring_config.weights.band_high_threshold }}, MEDIUM ≥ {{ scoring_config.weights.band_medium_threshold }}, LOW ≥ {{ scoring_config.weights.band_low_threshold }}, INFO < {{ scoring_config.weights.band_low_threshold }}

{% if scoring_config.weights.vex_resolved %}

VEX status penalty

  • Findings with status NOT_AFFECTED, RESOLVED, or RESOLVED_WITH_PEDIGREE: {{ scoring_config.weights.vex_resolved }} points
  • Applies to both gated and additive-scored findings
  • Allows triaged findings to drop out of critical gates without being removed from the report
{% endif %} {% endif %}

Reachability interpretation

  • REACHABLE (score > 0): Code path is reachable — vulnerability can be triggered
  • INCONCLUSIVE (score = 0): Reachability analysis ran but was inconclusive
  • UNKNOWN (no data): Reachability analysis was never run
  • UNREACHABLE (score < 0): Code path cannot be triggered
{# -------------------- AI Prompts (--ai-prompts flag) -------------------- #} {% if ai_portfolio_prompt %}
{{ fs.panel_head('AI prompts', 'paste into any LLM') }}

Copy and paste into any LLM for remediation guidance. No API key required. Project, component, and finding prompts are in the tables below.

Portfolio remediation prompt
{{ ai_portfolio_prompt }}
{% endif %} {# -------------------- AI Portfolio Summary -------------------- #} {% if ai_portfolio_summary and not is_single_project %}
{{ fs.panel_head('AI portfolio remediation summary') }}
{% endif %} {# -------------------- AI Project Guidance -------------------- #} {% if ai_project_summaries %}
{{ fs.panel_head('AI project remediation guidance') }} {% for project_name, summary in ai_project_summaries.items() %}

{{ project_name }}

{% endfor %}
{% endif %} {# -------------------- AI Component Guidance Table -------------------- #} {% if ai_component_guidance %}

AI component remediation guidance

Critical and High priority components
{% for comp_key, guidance in ai_component_guidance.items() %} {% endfor %} {% if ai_component_guidance|length > 10 %} {% endif %}
Component Fix Version Rationale Guidance Workaround Code Search Confidence
{{ comp_key }} {{ guidance.fix_version|default('—') }}
{{ guidance.rationale|default('—') }}
{{ guidance.guidance|default('—') }}
{{ guidance.workaround|default('—') }}
{{ guidance.code_search_hints|default('—') }} {{ guidance.confidence|default('—') }}
{% endif %} {# -------------------- AI Finding Guidance Table -------------------- #} {% if ai_finding_guidance %}

AI finding triage guidance

Critical and High priority findings
{% for finding_id, guidance in ai_finding_guidance.items() %} {% endfor %} {% if ai_finding_guidance|length > 10 %} {% endif %}
Finding Priority Action Rationale Fix Version Workaround Code Search Confidence
{{ finding_id }} {{ guidance.priority|default('—') }}
{{ guidance.action|default('—') }}
{{ guidance.rationale|default('—') }}
{{ guidance.fix_version|default('—') }}
{{ guidance.workaround|default('—') }}
{{ guidance.code_search_hints|default('—') }} {{ guidance.confidence|default('—') }}
{% endif %} {# -------------------- Project Summary Table -------------------- #} {% if project_summary_df is defined and project_summary_df|length > 0 %}

Project risk summary

{{ project_summary_df|length }} projects
{% if folder_path %}{% endif %} {% if ai_project_prompts %}{% endif %} {% for row in project_summary_df %} {% if folder_path %}{% endif %} {% if ai_project_prompts %} {% endif %} {% endfor %} {% if render_mode != 'fragment' and project_summary_df|length > 10 %} {% endif %}
FolderProject CRIT HIGH MED LOW INFO Total Avg ScoreAI Prompt
{{ row.folder_name if row.folder_name is defined else '' }} {% if domain 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_findings|default(0) }} {{ row.avg_score|default(0) }} {% if row.project_name in ai_project_prompts %}
View
{{ ai_project_prompts[row.project_name] }}
{% else %}—{% endif %}
{% endif %} {# -------------------- Top Components Table -------------------- #} {% if top_components is defined and top_components|length > 0 %}

Top riskiest components

{{ top_components|length }} components
{% if ai_component_prompts %}{% endif %} {% for row in top_components %} {% if ai_component_prompts %} {% endif %} {% endfor %} {% if render_mode != 'fragment' and top_components|length > 10 %} {% endif %}
Component Version CRIT HIGH MED LOW Total Avg MaxAI PromptActions
{{ row.component_name }} {{ row.component_version }} {% 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.total_findings|default(0) }} {{ row.avg_score }} {{ row.max_score }} {% set comp_key = row.component_name ~ ':' ~ row.component_version %} {% if comp_key in ai_component_prompts %}
View
{{ ai_component_prompts[comp_key] }}
{% else %}—{% endif %}
{% set c_key = row.component_name ~ ':' ~ row.component_version %} {% set c_guidance = ai_component_guidance.get(c_key, {}) if ai_component_guidance is defined and ai_component_guidance else {} %} {% set c_summary_parts = [] %} {% if c_guidance.get('fix_version') %}{% if c_summary_parts.append('Fix: ' + c_guidance.fix_version) %}{% endif %}{% endif %} {% if c_guidance.get('guidance') %}{% if c_summary_parts.append(c_guidance.guidance) %}{% endif %}{% endif %} {% if c_guidance.get('workaround') %}{% if c_summary_parts.append('Workaround: ' + c_guidance.workaround) %}{% endif %}{% endif %} {% set c_summary = c_summary_parts | join('\n\n') if c_summary_parts else (row.total_findings | string ~ ' findings (' ~ row.CRITICAL | string ~ ' CRITICAL, ' ~ row.HIGH | string ~ ' HIGH) in ' ~ row.component_name ~ ' ' ~ row.component_version) %} {% set c_band = 'CRITICAL' if (row.CRITICAL|default(0)) > 0 else ('HIGH' if (row.HIGH|default(0)) > 0 else ('MEDIUM' if (row.MEDIUM|default(0)) > 0 else 'LOW')) %}
{% endif %} {# -------------------- Findings Detail Table -------------------- #} {% if findings_df is defined and findings_df|length > 0 %}
{# Compound-bundle compaction. Gate the row cap + dropped columns on ``fragment_scripts_enabled`` (only the compound assembler sets it True; see compound-reports spec § 5) rather than ``render_mode == 'fragment'`` alone. Single-recipe fragment consumers (forge MCP, email digests) DON'T set fragment_scripts_enabled, so they keep the full top-100 + all 17 columns. Only the compound bundle trades detail for page count. (Round-1 multi- review M1-3.) #} {# _is_bundle / _findings_limit / _findings_slice are hoisted to the top of the template — the sidebar band/quick-filter counts consume the slice before this table renders. #}

Findings · top {{ _findings_limit }} by priority

sorted by triage_score desc
{% if not _is_bundle %} {% endif %} {% if ai_finding_prompts and not _is_bundle %}{% endif %} {% for row in _findings_slice %} {# In fragment mode every row is visible (capped at 25); standalone keeps the JS show-more toggle. data-* attributes drive the sidebar band/quick filters (fsApplyTriageFilters). #} {% set _row_untriaged = 0 if (row.status is defined and row.status and row.status not in ('', 'nan', 'None')) else 1 %} {% set _additive = (row._pts_reachability|default(0) + row._pts_exploit|default(0) + row._pts_vector|default(0) + row._pts_epss|default(0) + row._pts_cvss|default(0))|round(1) %} {% set _gate_bonus = (row.triage_score - _additive)|round(1) if row.gate_assignment != 'NONE' else 0 %} {% if not _is_bundle %} {% endif %} {% if ai_finding_prompts and not _is_bundle %} {% endif %} {% endfor %} {% if render_mode != 'fragment' and _findings_slice|length > 10 %} {% endif %}
CVE Band Score Gate Component Project Version ReachReach Score Vuln FunctionsExploit KEV Vector EPSS CVSSAI PromptVEX Actions
{% if domain and row.project_id and row.project_version_id and row.internal_id %} {{ row.finding_id }} {% else %}{{ row.finding_id }}{% endif %} {{ row.priority_band }}{{ row.triage_score }} {{ row.gate_assignment if row.gate_assignment != 'NONE' else '—' }} {% if domain and row.project_id and row.project_version_id and row.component_id %} {{ row.component_name }}{% if row.component_version %} {{ row.component_version }}{% endif %} {% else %}{{ row.component_name }}{% if row.component_version %} {{ row.component_version }}{% endif %}{% endif %} {% if domain and row.project_id %}{{ row.project_name }}{% else %}{{ row.project_name }}{% endif %} {% if domain and row.project_id and row.project_version_id %}{{ row.version_name if row.version_name else '—' }}{% else %}{{ row.version_name if row.version_name else '—' }}{% endif %} {% set rlabel = row.reachability_label if row.reachability_label is string else 'UNKNOWN' %} {{ rlabel }} {{ row.reachability_score|int if row.reachability_score else '—' }} {{ row.vuln_functions if row.vuln_functions else '—' }}{% if row.has_exploit %}exploit{% else %}{% endif %} {% if row.in_kev %}kev{% else %}{% endif %} {{ row.attack_vector }} {{ "%.1f"|format(row.epss_percentile|default(0) * 100) if row.epss_percentile else '—' }}% {{ row.risk }} {% if row.finding_id in ai_finding_prompts %}
View
{{ ai_finding_prompts[row.finding_id] }}
{% else %}—{% endif %}
{% if row.status is defined and row.status and row.status not in ('', 'nan', 'None') %} {{ row.status }} {% elif vex_rec_lookup is defined and vex_rec_lookup and row.finding_id in vex_rec_lookup %} {% set vrec = vex_rec_lookup[row.finding_id] %} {{ vrec.recommended_vex_status }} (rec) {% else %}{% endif %} {% set f_guidance = ai_finding_guidance.get(row.finding_id, {}) if ai_finding_guidance is defined and ai_finding_guidance else {} %} {% set f_summary_parts = [] %} {% if f_guidance.get('fix_version') %}{% if f_summary_parts.append('Fix: ' + f_guidance.fix_version) %}{% endif %}{% endif %} {% if f_guidance.get('guidance') %}{% if f_summary_parts.append(f_guidance.guidance) %}{% endif %}{% endif %} {% if f_guidance.get('workaround') %}{% if f_summary_parts.append('Workaround: ' + f_guidance.workaround) %}{% endif %}{% endif %} {% set f_reach = row.reachability_label if row.reachability_label is string else 'UNKNOWN' %} {% set f_summary = f_summary_parts | join('\n\n') if f_summary_parts else (row.severity ~ ' vulnerability in ' ~ row.component_name ~ ' ' ~ row.component_version ~ '. Reachability: ' ~ f_reach ~ '.') %} {% if row.internal_id and row.project_version_id %} {% set vex_reason = vex_reason_lookup.get(row.finding_id, '') if vex_reason_lookup is defined and vex_reason_lookup else '' %} {% set rec_data = vex_rec_lookup.get(row.finding_id) if vex_rec_lookup is defined and vex_rec_lookup else None %} {% endif %}
{% endif %}
{# ============================================================ STATUS BAR — bottom strip ============================================================ #} {% if render_mode != 'fragment' %} {{ fs.status_bar( items=[ recipe_name|default('Triage Prioritization'), (metadata.transformed_count|string ~ ' records') if metadata and metadata.transformed_count else 'no records', generated_at|default('') ], version='fs-report ' ~ (fs_report_version | default('dev')) ) }} {% endif %} {% if ai_component_guidance or ai_finding_guidance %}
This product uses the NVD API but is not endorsed or certified by the NVD. Fix version data is derived from NVD CPE match criteria and may be reformatted. Verify fix versions against vendor advisories before deploying updates.
{% endif %} {% if render_mode != 'fragment' %} {% include '_action_buttons.html' %} {% endif %}