{# Defensive defaults block — placed before the render-mode gate per C.1 round-2 multi-review M1-2. Mirrors all prior Phase 1b migrations. #} {% set cra_sections = cra_sections | default([]) %} {# Collection-level guard (C.2 round-2 M1-2): coerce non-list values to [] so the loop doesn't crash on upstream data drift. #} {% if cra_sections is mapping or cra_sections is string or cra_sections is not iterable %}{% set cra_sections = [] %}{% endif %} {% set sla_breach_count = sla_breach_count | default(0) %} {% set kpi_period = kpi_period | default('—') %} {% set kpi_total = kpi_total | default(0) %} {% set kpi_overdue = kpi_overdue | default(0) %} {% set kpi_due_soon = kpi_due_soon | default(0) %} {% set kpi_unknown_clock = kpi_unknown_clock | default(0) %} {% set kpi_in_triage = kpi_in_triage | default(0) %} {% set has_reachability = has_reachability | default(false) %} {% set kpi_reachable = kpi_reachable | default(0) %} {% set scope_label = scope_label | default('All projects') %} {% set threshold_label = threshold_label | default('—') %} {% set since_label = since_label | default('24h') %} {% import "_console_macros.html" as fs with context %} {% if render_mode != 'fragment' %} {% if domain %}{% endif %} CRA Compliance Report - Finite State Report {# Recipe is chart-free — no ECharts CDN. #} {% include "_design_system.html" %} {% include "_console_shell.html" %} {% endif %} {# Recipe-local styles — render in BOTH standalone and fragment modes so fragment_extractor (_STYLE_RE) collects them, scopes under .fs-section-cra-compliance, and re-emits at the top of the fragment. All hex literals migrated to design tokens where available; the .fs-section-cra-compliance prefix on each rule is DROPPED (auto-scoping handles it in fragment mode; .cra-* class names are unique in standalone HTML). #} {% if render_mode != 'fragment' %} {% endif %} {# Build topbar meta items. #} {% set cra_meta = [{"label": "Domain", "value": domain or "—"}] %} {% set _ = cra_meta.append({"label": "Scope", "value": scope_label}) %} {% set _ = cra_meta.append({"label": "Threshold", "value": threshold_label}) %} {% set _ = cra_meta.append({"label": "Since", "value": since_label}) %} {% set _ = cra_meta.append({"label": "Generated", "value": generated_at or "—"}) %} {% if render_mode != 'fragment' %} {{ fs.topbar( crumbs=["Finite State", "CRA Compliance"], meta=cra_meta, controls=[], print_brand=false, ) }} {% endif %}

CRA Compliance Report

EU Cyber Resilience Act Article 14 — Exploit Advancement Notification

{# Print-only metadata block — the topbar is display:none in print so this restores the Domain/Scope/Threshold/Since/Generated chips that the legacy `.metadata` div used to provide. Hidden on screen (topbar handles that there). #}
Domain: {{ domain | default('—') }}  |  Scope: {{ scope_label | default('All projects') }}  |  Generated: {{ generated_at | default('—') }}  |  Threshold: {{ threshold_label | default('—') }}  |  Since: {{ since_label | default('24h') }}
{# How to use this report callout — preserved verbatim. #}

How to use this report

This report alerts you when exploits for vulnerabilities in your portfolio advance in maturity — newly added to CISA KEV, becoming weaponized, or gaining ransomware/threat-actor attribution. Use it to:

  1. Identify which of your products contain the affected components.
  2. Test those products to verify exposure.

Under EU CRA Article 14, manufacturers must notify ENISA within 24 hours of becoming aware of an actively exploited vulnerability in a product with digital elements. The 🔥 SLA-Breach Risk section flags findings carrying platform exploitation signals — potential candidates to investigate and disposition (confirm affected, or mark NOT_AFFECTED / VEX) before that 24-hour clock is treated as running.

{# Warning / OK banner. #} {% if sla_breach_count and sla_breach_count > 0 %}
⚠️ {{ sla_breach_count }} potential actively-exploited vulnerabilit{% if sla_breach_count == 1 %}y{% else %}ies{% endif %} detected. These are platform KEV / known-exploit signals — investigate and disposition each one (confirm affected, or mark NOT_AFFECTED / VEX) before treating the EU CRA Article 14 24-hour ENISA notification clock as running.
{% else %}
✅ No CRA-relevant findings requiring immediate notification.
{% endif %} {# 7 KPI cells via fs.kpi_cell. #}
{{ fs.kpi_cell("Period", kpi_period) }} {{ fs.kpi_cell("Total Findings", kpi_total) }} {{ fs.kpi_cell( "🔥 OVERDUE", kpi_overdue, dot=("critical" if kpi_overdue > 0 else None), ) }} {{ fs.kpi_cell( "DUE_SOON", kpi_due_soon, dot=("high" if kpi_due_soon > 0 else None), ) }} {{ fs.kpi_cell( "Unknown Clock", kpi_unknown_clock, dot=("high" if kpi_unknown_clock > 0 else None), ) }} {% if has_reachability %} {{ fs.kpi_cell("Reachable", kpi_reachable) }} {% endif %} {{ fs.kpi_cell("In Triage", kpi_in_triage) }}
{# Per-section callout texts (preserved verbatim from pre-migration template lines 203-209). #} {% set _section_callouts = { "sla_breach": "These vulnerabilities carry platform exploitation signals and are flagged as potentially actively exploited — investigate and disposition each (confirm affected, or mark NOT_AFFECTED / VEX) before treating the 24-hour clock as running. The KEV Source column shows which platform signal triggered: CISA means the CVE is in the public CISA KEV catalog, VcKEV means the CVE is in VulnCheck's KEV catalog (broader than CISA's), CISA+VcKEV means both. The 24-hour CRA notification clock anchors on the later of CISA's dateAdded (when the CISA catalog join succeeds for this row) and the platform's detected_date (when FS first observed this finding). If Notification Deadline is empty, neither timestamp was available — the row is flagged Unknown Clock and the 24-hour clock cannot be computed. Identify which of your products contain the listed components and either confirm a mitigation or file the ENISA notification.", "newly_above": "Exploit signals for these CVEs advanced in maturity during the report window. They weren't CRA-relevant before, but a new KEV listing / weaponization / ransomware or threat-actor attribution puts them in scope now. Treat as the 'what changed today' list — identify affected products and start the triage.", "re_emerged": "CVEs you previously marked resolved or not-affected that have gained a new exploit signal. Reverify the mitigation still holds. The previous_resolution column shows how the finding was originally closed.", "still_in_triage": "Open triage items not yet decided. triage_age_days shows how long each has been pending. The risk: if exploit maturity advances on any of these, they jump to 🆕 or 🔥 and the 24-hour clock starts.", "full_snapshot": "Complete inventory of CRA-relevant findings in scope. Reference list, not action items — the 🔥/🆕/🔁/⏰ sections above contain the rows that need attention this run." } %} {# 5 morning-queue sections. The Full Snapshot section gets a page-break anchor in print so dense multi-section output paginates to ~2 pages, restoring legacy layout (the @media print rule on .cra-full-snapshot-anchor sets page-break-before: always). #} {% for section in cra_sections %} {% if section is mapping %} {{ section.label }} ({{ section.count }}) {% if _section_callouts[section.key] is defined %}

{{ _section_callouts[section.key] | safe }}

{% endif %} {% if section.count and section.count > 0 %} {% for col in section.columns %}{% endfor %} {% for row in section.rows %} {# Per-row mapping guard (D round-1 PR multi-review M1-2 / M2-2 / M3-1): silently skip non-dict rows so upstream data drift renders cleanly instead of crashing or producing broken cells. #} {% if row is mapping %} {% for col in section.columns %} {% set cell_val = row[col.key] %} {% if col.key == "severity" and cell_val and cell_val not in ("", "None", "nan") %} {% elif cell_val is none or cell_val == "None" or cell_val == "nan" or cell_val == "" %} {% else %} {% endif %} {% endfor %} {% endif %} {% endfor %}
{{ col.display }}
{{ cell_val }}{{ cell_val }}
{% if section.truncated %}

Showing top {{ section.shown }} of {{ section.count }} — see CSV/XLSX for full set.

{% endif %} {% else %}

No findings in this section.

{% endif %} {% endif %} {% endfor %} {# CISA-vs-CRA footnote — preserved verbatim. #}

Note on SLA columns: The CRA notification clock (cra_notification_at + 24h) is independent of CISA's US federal BOD 22-01 remediation deadline (cisa_remediation_due). The two SLAs serve different regulators.

{# Print-only footer — the status_bar is display:none in print so this restores the "Generated by Finite State | " line + the copyright that the legacy _default_footer.html used to provide. Hidden on screen (status_bar handles that there). #} {% set _year = (generated_at[:4] if generated_at and generated_at[:4].isdigit() else '2026') %}
{% if render_mode != 'fragment' %} {{ fs.status_bar(items=[ "Recipe: CRA Compliance", "Findings: " ~ (kpi_total | string), "SLA-Breach: " ~ (sla_breach_count | string), generated_at | default(''), ], version='fs-report ' ~ (fs_report_version | default('dev'))) }} {% endif %} {% if render_mode != 'fragment' %} {% include '_action_buttons.html' %} {% endif %}