fix(override): pin exploitation_source on any field change, not just cvss
Tester reported only 4 hits for filter exploitation_source='vulnrichment' AND exploitation_status != 'none' while ssvc_technical_impact='total' returned 4682 and ssvc_automatable='yes' returned 842 rows. Mismatch by orders of magnitude. Root cause: _apply_single_override only set vuln.exploitation_source inside the CVSS and severity change blocks. SSVC writes (exploitation_status, ssvc_technical_impact, ssvc_automatable) went through their own branches without touching the source label. So a CVE whose Wazuh CVSS happened to already match Vulnrichment got SSVC fields written but exploitation_source stayed NULL. Two-part fix: 1. _apply_single_override now sets exploitation_source whenever ANY tracked field changed (single guard at the end of the function replaces the two redundant assignments inside CVSS/severity blocks — they still work because changes['has_changes'] is True there). 2. Migration 022 backfills exploitation_source='vulnrichment' on every row that has ANY SSVC field populated but no source yet. Idempotent. Existing nvd / cvelistv5 / manual source labels are not touched (WHERE exploitation_source IS NULL). After deploy + alembic upgrade head, the tester's filter will return the real count (~840 SSVC-marked CVEs from vulnrichment, not just the 4 with CVSS-diff coincidence).
This commit is contained in:
@@ -0,0 +1,48 @@
|
|||||||
|
"""Backfill exploitation_source for SSVC-only override rows
|
||||||
|
|
||||||
|
Revision ID: 022
|
||||||
|
Revises: 021
|
||||||
|
Create Date: 2026-05-20 11:00:00.000000
|
||||||
|
|
||||||
|
Earlier override-service runs wrote SSVC fields (ssvc_technical_impact,
|
||||||
|
ssvc_automatable, exploitation_status) without setting
|
||||||
|
exploitation_source. The pin only happened when CVSS or severity
|
||||||
|
changed.
|
||||||
|
|
||||||
|
Result: operator filter
|
||||||
|
WHERE exploitation_source='vulnrichment' AND exploitation_status != 'none'
|
||||||
|
under-reported by orders of magnitude (4 vs ~840 in tester's DB).
|
||||||
|
|
||||||
|
Stamp those rows as vulnrichment-sourced so the filter works. Only
|
||||||
|
touch rows that have at least one Vulnrichment-supplied SSVC field
|
||||||
|
AND no exploitation_source yet — does not overwrite an existing
|
||||||
|
source label (e.g. nvd / cvelistv5 / manual).
|
||||||
|
|
||||||
|
Idempotent — second run finds no candidates.
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
|
||||||
|
revision = '022'
|
||||||
|
down_revision = '021'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
op.execute("""
|
||||||
|
UPDATE vulnerabilities
|
||||||
|
SET exploitation_source = 'vulnrichment'
|
||||||
|
WHERE exploitation_source IS NULL
|
||||||
|
AND (
|
||||||
|
exploitation_status IS NOT NULL
|
||||||
|
OR ssvc_technical_impact IS NOT NULL
|
||||||
|
OR ssvc_automatable IS NOT NULL
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# Cannot reliably distinguish backfilled rows from real vulnrichment
|
||||||
|
# writes, so the downgrade is a no-op.
|
||||||
|
pass
|
||||||
@@ -884,6 +884,16 @@ class VulnOverrideService:
|
|||||||
vuln.ssvc_automatable = verified.ssvc_automatable
|
vuln.ssvc_automatable = verified.ssvc_automatable
|
||||||
changes["has_changes"] = True
|
changes["has_changes"] = True
|
||||||
|
|
||||||
|
# Source pin — set exploitation_source whenever ANY override field
|
||||||
|
# changed, not just CVSS/severity. Earlier behaviour only pinned the
|
||||||
|
# source on CVSS/severity diffs, so a CVE whose Wazuh CVSS happened
|
||||||
|
# to already match Vulnrichment got SSVC fields written but
|
||||||
|
# exploitation_source stayed NULL. Tester filter
|
||||||
|
# WHERE exploitation_source='vulnrichment' AND exploitation_status != 'none'
|
||||||
|
# then under-reported by orders of magnitude (4 vs the real ~840).
|
||||||
|
if changes["has_changes"] and not dry_run:
|
||||||
|
vuln.exploitation_source = verified.source or "vulnrichment"
|
||||||
|
|
||||||
return changes
|
return changes
|
||||||
|
|
||||||
def correct_all_vulnerabilities(
|
def correct_all_vulnerabilities(
|
||||||
|
|||||||
Reference in New Issue
Block a user