Vulnerability Management
Overview
Vulnerability management is a critical pillar of proactive security operations. While the Alerts and Incidents modules handle real-time threat response, the Vulnerability Management module addresses the underlying attack surface — the known weaknesses in infrastructure, applications, and endpoints that adversaries can exploit before they do.
This module provides a full vulnerability lifecycle management system: asset inventory tracking, multi-scanner ingestion (Tenable, Qualys, Rapid7), risk-based prioritization using CVSS + EPSS scores with CISA Known Exploited Vulnerabilities (KEV) correlation, remediation SLA tracking with per-severity deadlines, a severity-vs-exploitability risk matrix heatmap, and trending velocity metrics (discovery rate, remediation rate, MTTR by week).
What Was Proposed
- Asset inventory management with hostname, IP, OS, criticality, owner, and department
- Vulnerability tracking with CVE ID, CVSS score, EPSS score, CISA KEV status, and affected asset mapping
- Risk-based prioritization engine combining CVSS, EPSS, and KEV factors into a composite risk score
- Multi-scanner import support for Tenable, Qualys, and Rapid7 scan result normalization
- CISA KEV catalog synchronization (live HTTP fetch from CISA)
- Remediation SLA enforcement: critical=15 days, high=30 days, medium=90 days, low=180 days
- Severity vs exploitability risk matrix for dashboard heatmap visualization
- Trending analytics: discovery velocity, remediation velocity, and MTTR trend by week
- Status lifecycle: open → in_progress → remediated / accepted / risk_accepted
- CSV bulk import for asset inventory
What's Built
| Asset registration and inventory (hostname, IP, OS, criticality, owner, dept, tags) | ✓ Complete |
| List assets with department and criticality filters | ✓ Complete |
| Get asset by ID with associated vulnerabilities | ✓ Complete |
| Vulnerability CRUD (create via scan import, read, update status) | ✓ Complete |
| Vulnerability list with severity, status, asset_id, and CVE filters | ✓ Complete |
| Risk-based prioritization endpoint (sorted by composite risk score) | ✓ Complete |
| Severity vs exploitability risk matrix endpoint | ✓ Complete |
| Dashboard statistics endpoint (total, critical open, high open, avg risk, SLA compliance) | ✓ Complete |
| Remediation SLA compliance endpoint (per-severity tier) | ✓ Complete |
| Trending endpoint (discovery velocity, remediation velocity, MTTR by week) | ✓ Complete |
| CISA KEV status check endpoint (by CVE ID) | ✓ Complete |
| CISA KEV catalog sync (live HTTP fetch from CISA endpoint) | ✓ Complete |
| Multi-scanner import: Tenable, Qualys, Rapid7 normalization | ✓ Complete |
| CSV bulk asset import | ✓ Complete |
| Pre-loaded demo data (8 vulnerabilities, 8 assets) for dashboard display | ✓ Complete |
| Frontend: 5-column stats bar (total, critical open, high open, avg risk, SLA compliance) | ✓ Complete |
| Frontend: severity vs exploitability heatmap table | ✓ Complete |
| Frontend: SLA compliance progress bars per severity tier | ✓ Complete |
| Frontend: prioritized vulnerabilities table with CVE search, severity/status filters, sortable columns | ✓ Complete |
| Frontend: asset inventory table with drill-down to asset detail + associated vulns | ✓ Complete |
| Frontend: three mini bar chart trend sparklines (discovery, remediation, MTTR) | ✓ Complete |
Architecture
Backend Service: VulnerabilityManager
File: app/services/vulnerability_mgmt.py — The VulnerabilityManager class is instantiated as a singleton (vuln_manager) at module import time. It holds two in-memory dictionaries (assets and vulnerabilities) and a KEV catalog dict that is populated via async HTTP fetch from CISA. Demo data is loaded in __init__ via _load_demo_data().
Key Methods
| Method | Description |
|---|---|
register_asset(asset) | Adds an asset to the in-memory dict and logs registration |
list_assets(department, criticality) | Returns filtered list of all assets |
get_asset(asset_id) | Returns a single Asset or None |
import_assets_from_csv(csv_data) | Bulk import from CSV text with expected columns: hostname, ip, os, criticality, owner, department, tags |
list_vulnerabilities(severity, status, asset_id, cve_id) | Filtered list of Vulnerability objects |
get_vulnerability(vuln_id) | Single vulnerability by ID or None |
update_status(vuln_id, status, notes, assigned_to) | Updates status, appends note, sets assigned_to, updates timestamps |
prioritize_vulnerabilities() | Returns open/in-progress vulns sorted by composite risk_score descending |
get_risk_matrix() | Builds severity x exploitability count matrix for heatmap |
get_vuln_stats() | Dashboard stats: total, critical_open, high_open, avg_risk_score, sla_compliance |
get_sla_compliance() | Per-severity SLA compliance percentage and overdue count based on SLA_DAYS thresholds |
get_trending() | Weekly discovery velocity, remediation velocity, and MTTR trend data |
ingest_scan_results(scanner_type, results) | Normalizes and ingests results from Tenable, Qualys, or Rapid7 scan outputs |
check_kev_status(cve_id) | Returns KEV catalog entry if present, or a "not found" dict |
sync_kev_catalog() | Async HTTP GET to CISA KEV JSON feed, populates kev_catalog dict, returns sync result |
Remediation SLA Thresholds
SLA_DAYS = {
"critical": 15, # Must remediate within 15 days of discovery
"high": 30, # Must remediate within 30 days
"medium": 90, # Must remediate within 90 days
"low": 180, # Must remediate within 180 days
}
CISA KEV Integration
The KEV (Known Exploited Vulnerabilities) catalog is fetched from https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json using httpx.AsyncClient. The catalog is stored in vuln_manager.kev_catalog as a dict keyed by CVE ID. Vulnerabilities with cisa_kev=True receive an elevated risk priority in the prioritization sort. The catalog sync records a timestamp in kev_last_sync.
Scanner Normalization
The ingest_scan_results method accepts three scanner types and maps their field names to the internal Vulnerability dataclass. This allows raw scan output from different tools to be stored in a unified schema:
| Scanner | CVE Field | CVSS Field | Description Field |
|---|---|---|---|
| Tenable | plugin_name / CVE | cvss_base_score | synopsis |
| Qualys | QID / CVE_ID | CVSS_SCORE | TITLE |
| Rapid7 | cve_id / title | cvss_score | description |
API Endpoints
All endpoints are defined in app/routers/vulnerability.py under the prefix /api/v1.
Vulnerability Endpoints
GET /api/v1/vulnerabilities
# List all vulns with optional filters
# Query params: severity, status, asset_id, cve_id, skip=0, limit=100
GET /api/v1/vulnerabilities/prioritized?limit=50
# Active vulns sorted by risk_score descending
GET /api/v1/vulnerabilities/risk-matrix
# Severity vs exploitability count matrix for heatmap
GET /api/v1/vulnerabilities/stats
# Dashboard stats: total_vulns, critical_open, high_open, avg_risk_score, sla_compliance
GET /api/v1/vulnerabilities/sla
# Remediation SLA compliance per severity tier (15/30/90/180 day thresholds)
GET /api/v1/vulnerabilities/trending
# Weekly discovery_velocity, remediation_velocity, mttr_trend data points
GET /api/v1/vulnerabilities/kev/{cve_id}
# Check if a CVE ID is in the CISA KEV catalog (case-insensitive, normalized to uppercase)
GET /api/v1/vulnerabilities/{vuln_id}
# Get a single vulnerability by ID (404 if not found)
PATCH /api/v1/vulnerabilities/{vuln_id}
# Update status, notes, assigned_to
# Body: VulnStatusUpdate { status, notes?, assigned_to? }
# Valid statuses: open | in_progress | remediated | accepted | risk_accepted
# 400 on invalid status, 404 if not found
Scanner Integration
POST /api/v1/vulnerabilities/scan/import
# Import and normalize scan results
# Body: ScanImport { scanner_type: "tenable|qualys|rapid7", results: [...] }
# Returns: { status, scanner, vulnerabilities_processed, total_tracked }
POST /api/v1/vulnerabilities/kev/sync
# Sync CISA KEV catalog (async HTTP fetch from CISA)
# Returns: { synced: bool, count: int, last_sync: ISO timestamp }
Asset Endpoints
POST /api/v1/assets (HTTP 201)
# Register a new asset
# Body: AssetCreate { hostname, ip, os?, criticality?, owner?, department?, tags? }
GET /api/v1/assets
# List all assets with optional filters
# Query params: department, criticality, skip=0, limit=100
GET /api/v1/assets/{asset_id}
# Get asset with associated vulnerability list
# Returns: Asset fields + vulnerability_count + vulnerabilities: [...]
# 404 if not found
Routing
| Layer | Path | Description |
|---|---|---|
| /vulnerabilities | Frontend route (Next.js App Router) | Main vulnerability management page |
| /vulnerabilities/[id] | Frontend route (Next.js App Router) | Individual vulnerability detail page |
| /vulnerabilities/remediation | Frontend route (Next.js App Router) | Remediation tracking sub-page |
| /vulnerabilities/scanners | Frontend route (Next.js App Router) | Scanner integration management sub-page |
| /api/v1/vulnerabilities | API prefix (FastAPI router) | All vulnerability endpoints |
| /api/v1/assets | API prefix (FastAPI router) | Asset inventory endpoints |
Data Model
The Vulnerability Management module uses in-memory Python dataclasses. No SQLAlchemy ORM tables are used for this module. Data is pre-seeded with realistic demo entries on startup.
Asset Dataclass
| Field | Type | Description |
|---|---|---|
id | str | Auto-generated: asset-{8 hex chars} |
hostname | str | Asset hostname (e.g. "WEB-PROD-01") |
ip | str | IP address (e.g. "10.1.2.15") |
os | str | Operating system string (e.g. "Ubuntu 22.04") |
criticality | str | low / medium / high / critical |
owner | str | Responsible user or team (default: "unassigned") |
department | str | Department name (default: "IT") |
tags | list[str] | Descriptive tags for categorization |
last_scan | str or None | ISO 8601 timestamp of last vulnerability scan |
Vulnerability Dataclass
| Field | Type | Description |
|---|---|---|
id | str | Auto-generated UUID prefix (e.g. "vuln-abc12345") |
cve_id | str | CVE identifier (e.g. "CVE-2026-0217") |
title | str | Human-readable vulnerability title |
description | str | Technical description of the vulnerability |
cvss_score | float | CVSS v3 base score (0.0-10.0) |
epss_score | float | EPSS exploitability probability score (0.0-1.0) |
cisa_kev | bool | Whether this CVE is in the CISA Known Exploited Vulnerabilities catalog |
affected_assets | list[str] | List of asset IDs affected by this vulnerability |
severity | str | critical / high / medium / low (derived from CVSS) |
status | str | open / in_progress / remediated / accepted / risk_accepted |
remediation_deadline | str or None | ISO 8601 deadline based on SLA_DAYS thresholds from discovery date |
assigned_to | str or None | Username or team assigned for remediation |
discovered_at | str or None | ISO 8601 timestamp when vulnerability was first discovered |
remediated_at | str or None | ISO 8601 timestamp when remediation was confirmed |
notes | list[dict] | Audit log of status changes with timestamps and notes text |
risk_score | float | Composite risk score (0-100) used for prioritization sort. Combines CVSS (weight 40%), EPSS (weight 35%), KEV flag (weight 25%). |
Demo Data (pre-loaded)
| CVE ID | Title | CVSS | EPSS | KEV | Risk |
|---|---|---|---|---|---|
| CVE-2026-0217 | RCE in Apache Struts | 9.8 | 0.94 | Yes | 98 |
| CVE-2026-1034 | SQL Injection in Custom Web App | 9.1 | 0.72 | No | 92 |
| CVE-2025-48271 | Priv Esc in Windows Print Spooler | 8.8 | 0.88 | Yes | 89 |
| CVE-2026-0089 | OpenSSL Buffer Overflow | 8.6 | 0.65 | Yes | 85 |
| CVE-2026-0445 | Auth Bypass in Fortinet FortiOS | 9.3 | 0.91 | Yes | 96 |
| CVE-2025-51122 | XSS in Confluence Server | 7.5 | 0.41 | No | 72 |
| CVE-2025-49901 | DoS in Nginx | 6.5 | 0.22 | No | 48 |
| CVE-2026-0812 | Info Disclosure in Exchange Server | 7.1 | 0.53 | No | 68 |
Prerequisites
- VulnerabilityManager singleton —
app/services/vulnerability_mgmt.pyimports and instantiatesvuln_managerat module level. The router imports this singleton directly. No database or migration required. - httpx — The
sync_kev_catalog()method requireshttpx(async HTTP client) to fetch the CISA KEV JSON feed. It is listed inrequirements.txt. If CISA is unreachable, the method returns an error response without raising. - Router registration —
app/main.pymust includeapp.include_router(vulnerability.router)for the endpoints to be served. Both/api/v1/vulnerabilitiesand/api/v1/assetsare registered under the same router with prefix/api/v1. - Frontend API Client —
src/lib/api-client.tsprovidesapi.get()used for the six parallel data fetches on page mount. Each individual call uses.catch(() => null)so a single endpoint failure does not break the entire page. Mock data is used as initial state and updated only when API returns valid data.
UI Layout
Page Sections (top to bottom)
- Page Header — Orange Bug icon + "Vulnerability Management" h1. Loading spinner shown while data is being fetched.
- Stats Bar — 5-column grid of metric cards: Total Vulns (indigo), Critical Open (red), High Open (orange), Avg Risk Score (yellow), SLA Compliance % (green). Each card has a colored icon tile, bold value, and label.
- Risk Matrix + SLA Compliance (side by side):
- Risk Matrix — Table with 4 severity rows (Critical, High, Medium, Low) and 3 exploitability columns (High, Medium, Low). Each cell shows a count. Cell background color is a heatmap: red for high intensity (>60% of max), orange for medium, amber for low, slate for near-zero. Cell font is bold white or dark depending on background.
- SLA Compliance — 4 rows (Critical/High/Medium/Low) each showing severity name, SLA day threshold (e.g. "15d SLA"), overdue count, compliance percentage (colored), and a progress bar filled to compliance percentage in the severity color.
- Tab Bar — "Prioritized Vulnerabilities" and "Asset Inventory" tabs. Active tab uses orange underline and text color. Tab switch clears asset drill-down state.
- Prioritized Vulnerabilities Tab:
- Filter row: CVE/title search input (with Search icon), severity dropdown (critical/high/medium/low by CVSS range), status dropdown (open/in_progress/remediated/accepted), and result count label.
- Table columns: CVE ID (orange monospace), Title (truncated), CVSS (colored by score: red ≥9.0, orange ≥7.0, yellow ≥4.0), EPSS (as percentage), CISA KEV (red "Yes" / gray "No" badge), Risk Score (progress bar + bold colored value), Assets (count), Status (colored badge), Assigned.
- Sortable columns: CVE ID, CVSS, EPSS, Risk Score (click to toggle asc/desc). Default sort: risk_score descending.
- Asset Inventory Tab:
- Table with columns: Hostname (Monitor icon), IP (monospace), OS, Criticality (colored badge), Vuln Count, Last Scan. Rows are clickable (cursor-pointer).
- Click on a row drills down to an asset detail view showing hostname, IP, OS, a 3-column stat grid (vuln count, criticality, last scan), and a table of associated vulnerabilities.
- Back button in the detail view clears the selection and returns to the asset list.
- Trending Section:
- 3-column grid of mini bar chart sparklines: Discovery Velocity (red bars, ArrowUpRight icon), Remediation Velocity (green bars, ArrowUpRight icon), MTTR Trend in days (blue bars, ArrowDownRight icon with "Improving" label).
- Each chart renders 6 weekly data points with week labels (W5-W10) using a custom
MiniBarChartCSS component (no external charting library).