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
|
||||
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
|
||||
|
||||
def correct_all_vulnerabilities(
|
||||
|
||||
Reference in New Issue
Block a user