<!-- Copyright (c) 2026 defconxt. All rights reserved. -->
<!-- Licensed under AGPL-3.0 — see LICENSE file for details. -->
---
name: implementing-vulnerability-metrics-reporting
description: >-
  Build vulnerability management dashboards and executive reports covering KPIs
  like MTTR, SLA adherence, scan coverage, risk reduction trends, and finding
  density. Automate data collection from Nessus, OpenVAS, and nuclei.
domain: cybersecurity
subdomain: vulnerability-management
tags:
  - metrics
  - reporting
  - kpis
  - dashboards
  - risk-reduction
  - executive-reporting
version: "1.0"
author: defconxt
license: AGPL-3.0
metadata:
  mitre-attack: []
---

# Implementing Vulnerability Metrics Reporting

## Overview

Metrics transform vulnerability management from a reactive scanning exercise into
a measurable security program. Track key indicators — MTTR, scan coverage, SLA
adherence, risk density — to demonstrate program effectiveness to leadership and
identify systemic remediation gaps.

## Prerequisites

| Requirement | Install |
|---|---|
| Python 3.10+ | Agent tooling and data processing |
| jq | `apt install jq` or `brew install jq` |
| Nessus Professional | Scan data export |
| matplotlib | `pip install matplotlib` (optional, for charts) |

## Key Concepts

### Core KPIs

| KPI | Definition | Target |
|---|---|---|
| MTTR | Median days from discovery to remediation | Critical: <3d, High: <7d |
| SLA Adherence | % findings remediated within SLA deadline | >95% |
| Scan Coverage | % assets scanned in last 30 days | >98% |
| Risk Density | Open critical+high findings per 100 assets | <5 |
| Recurrence Rate | % previously fixed findings that reappear | <3% |
| Exception Rate | % findings with active exceptions | <5% |
| Detection Latency | Days between CVE publication and first scan detection | <7d |

### Data Collection

```bash
# Export Nessus scan history for trend analysis
for SCAN_ID in $(curl -sk -H "X-ApiKeys: accessKey=$NESSUS_ACCESS;secretKey=$NESSUS_SECRET" \
  "https://localhost:8834/scans" | jq -r '.scans[].id'); do
  curl -sk -H "X-ApiKeys: accessKey=$NESSUS_ACCESS;secretKey=$NESSUS_SECRET" \
    "https://localhost:8834/scans/$SCAN_ID/export" \
    -d 'format=csv' -o "scan_${SCAN_ID}.csv"
done

# nuclei scan statistics
nuclei -u https://target.example.com -stats -json -o scan_stats.json 2>&1 | \
  grep "stats" | jq '{templates: .templates, hosts: .hosts, matched: .matched}'

# OpenVAS export via GVM CLI
gvm-cli --gmp-username admin --gmp-password $GVM_PASS socket \
  --socketpath /run/gvmd/gvmd.sock \
  --xml '<get_reports report_id="$REPORT_ID" format_id="a994b278-1f62-11e1-96ac-406186ea4fc5"/>'
```

### Trend Calculation

```python
import json
from datetime import datetime
from collections import defaultdict

def calculate_trends(findings_history: list[dict]) -> dict:
    """Calculate monthly vulnerability trends."""
    monthly: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int))
    for f in findings_history:
        month = datetime.fromisoformat(f["discovered"]).strftime("%Y-%m")
        severity = f.get("severity", "unknown")
        monthly[month][severity] += 1
    return dict(monthly)

def calculate_mttr(closed_findings: list[dict]) -> dict:
    """Calculate MTTR by severity."""
    durations: dict[str, list[int]] = defaultdict(list)
    for f in closed_findings:
        if f.get("resolved_date"):
            days = (datetime.fromisoformat(f["resolved_date"]) -
                    datetime.fromisoformat(f["discovered"])).days
            durations[f.get("severity", "unknown")].append(days)
    mttr = {}
    for sev, days in durations.items():
        sorted_d = sorted(days)
        mttr[sev] = {
            "median": sorted_d[len(sorted_d) // 2],
            "mean": round(sum(sorted_d) / len(sorted_d), 1),
            "p95": sorted_d[int(len(sorted_d) * 0.95)] if len(sorted_d) >= 20 else sorted_d[-1],
            "count": len(sorted_d),
        }
    return mttr
```

### Executive Report Template

```
VULNERABILITY MANAGEMENT — MONTHLY REPORT
Period: {month} {year}

EXECUTIVE SUMMARY
├── Open Findings: {total_open} ({delta} vs last month)
├── Critical/High Open: {crit_high_open}
├── MTTR (Critical): {mttr_critical} days (target: <3 days)
├── SLA Adherence: {sla_pct}% (target: >95%)
└── Scan Coverage: {coverage_pct}% (target: >98%)

RISK TREND
├── Month-over-month critical findings: {trend_direction}
├── New findings this month: {new_count}
├── Closed findings this month: {closed_count}
└── Net change: {net_change}

TOP 5 OVERDUE FINDINGS
1. {cve} — {asset} — {days_overdue} days overdue
...
```

## Workflow

1. **Collect** — Export scan data from all scanners (Nessus, nuclei, OpenVAS)
2. **Normalize** — Standardize finding format across scanner outputs
3. **Calculate** — Compute KPIs: MTTR, SLA adherence, coverage, density
4. **Trend** — Compare current period to previous for directional analysis
5. **Visualize** — Generate charts and executive summary
6. **Distribute** — Send report to stakeholders on defined cadence

## Verification

| Check | Method |
|---|---|
| Data complete | All scan sources included in aggregation |
| MTTR accurate | Spot-check 5 findings for correct date math |
| Coverage valid | Asset count matches CMDB inventory |
| Trends consistent | Month-over-month deltas sum correctly |
| Report generated | HTML/PDF output renders without errors |

## References

- [NIST SP 800-55 — Security Metrics Guide](https://csrc.nist.gov/publications/detail/sp/800-55/rev-1/final)
- [CIS Control 7 — Continuous Vulnerability Management](https://www.cisecurity.org/controls/continuous-vulnerability-management)
- [FIRST CVSS Calculator](https://www.first.org/cvss/calculator/4.0)
