Tester feature (step 1 of multi-source enrichment): augment the Nessus-
only scanner remediation with authoritative Microsoft data, for Windows
OS *and* MS products (Office/365, .NET, SQL, Exchange, ...).
MSRC's per-CVE endpoint 404s, so we ingest the monthly CVRF documents
(api.msrc.microsoft.com/cvrf/v3.0/cvrf/{YYYY-Mon}, ~4 MB each) and extract
per-CVE remediations:
- fixes: KB number + FixedBuild + download URL (Remediations Type 2/3)
- workarounds / mitigations (containment): Notes "Workarounds" /
"Mitigations", HTML stripped to text — covers the "no KB yet, only
containment" case the tester called out.
- Migration 032 + model: cve_remediations (CVE-level, source-tagged).
- app/services/msrc_service.py: refresh_msrc() pulls the last N monthly
docs (default 18, setting msrc_months_back), stores rows only for CVE
ids already in the DB (keeps it relevant). Re-parse replaces a CVE's
rows so MS revisions (containment-only -> KB later) self-update.
- Endpoints: GET /vulnerabilities/{id}/remediations (scanner + external,
grouped by source) and POST /vulnerabilities/msrc/refresh (fire-and-
forget background thread). Weekly scheduler job (Sun 04:40).
- UI: CVE detail now renders a Remediation block per source ("via scanner"
/ "via Microsoft (MSRC)") with KB + download links, workarounds, and
mitigation/containment. "🛡️ MSRC Enrich" button on the vuln list.
Verified parse against the live 2026-May CVRF doc (KB+build+catalog link
per Windows build). Migration 032 required: alembic upgrade head.
Step 2 (Linux: Ubuntu USN / CentOS errata) reuses cve_remediations next.
30 lines
1.2 KiB
Python
30 lines
1.2 KiB
Python
"""
|
|
CVE-level remediation enrichment from external primary sources.
|
|
|
|
Separate from the per-asset Vulnerability.remediation column (the Nessus
|
|
scanner solution): these rows are keyed by cve_id and come from MSRC CVRF
|
|
(Windows + MS products) and, later, Linux distro advisories. The CVE detail
|
|
page shows them alongside the scanner remediation, grouped by source.
|
|
"""
|
|
from sqlalchemy import Column, Integer, String, Text, DateTime, func
|
|
|
|
from app.models.base import Base
|
|
|
|
|
|
class CveRemediation(Base):
|
|
__tablename__ = "cve_remediations"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
cve_id = Column(String(50), nullable=False, index=True)
|
|
source = Column(String(32), nullable=False) # msrc | ubuntu | centos | ...
|
|
kind = Column(String(24), nullable=False) # fix | workaround | mitigation | advisory
|
|
title = Column(String(300), nullable=True)
|
|
detail = Column(Text, nullable=True)
|
|
kb = Column(String(64), nullable=True)
|
|
fixed_build = Column(String(120), nullable=True)
|
|
url = Column(Text, nullable=True)
|
|
fetched_at = Column(DateTime, nullable=False, server_default=func.now())
|
|
|
|
def __repr__(self):
|
|
return f"<CveRemediation(cve_id='{self.cve_id}', source='{self.source}', kind='{self.kind}')>"
|