Files
vulncheck/app/services/audit_events.py
T
vulncheck 00d22fc318 feat(audit): initial VULNERABILITY_DETECTED event for sync-created findings
Tester: a CVE newly created by a sync appeared in the vuln list but left
NO initial audit entry ("new CVE detected on asset X at ...") — the audit
trail only began with the first status change (open -> patched etc.).
Confirmed: not intentional, simply never built; fails the revisionssicher
requirement.

- Migration 031: ALTER TYPE auditeventtype ADD VALUE
  'VULNERABILITY_DETECTED' (idempotent, autocommit block).
- New app/services/audit_events.py: audit_new_vulnerabilities() writes one
  System/Auto event per newly created finding:
  "New finding detected: CVE-X on <hostname> (severity=..., source=...)".
- Wired into every creation path:
    * Wazuh full sync (run_wazuh_vulnerability_sync)  -> source=wazuh
    * Wazuh per-agent sync (sync_agent_vulnerabilities, covers the
      scheduler loop + per-asset rescan)              -> source=wazuh
    * Nessus sync (incl. Nessus EOL pseudo-vulns via
      newly_created_vuln_ids)                         -> source=nessus
    * endoflife.date upsert                           -> source=eol_check
    * M365 Apps upsert                                -> source=m365_check
  Full-sync and per-agent paths are independent (no double events).
- Best-effort: audit failure never breaks a sync.

Migration 031 required: alembic upgrade head.
2026-06-10 08:58:38 +02:00

61 lines
1.9 KiB
Python

"""
Shared audit-event writers for sync-driven changes.
Tester requirement (revisionssicher): a finding newly created by a
Wazuh/Nessus sync must leave an initial "VULNERABILITY_DETECTED" trail —
previously the audit history only began with the first status change.
"""
import logging
from datetime import datetime
from typing import Iterable
from sqlalchemy.orm import Session
from app.models.audit_log import AuditLog, AuditEventType
from app.models.vulnerability import Vulnerability
logger = logging.getLogger(__name__)
def audit_new_vulnerabilities(
db: Session,
vuln_ids: Iterable[int],
*,
source: str,
) -> int:
"""Write one VULNERABILITY_DETECTED audit row per newly created finding.
`source` names the detector ("wazuh", "nessus", "eol_check", ...).
user_id stays NULL → the audit UI renders it as System/Auto, matching
the asset-lifecycle events. Caller commits. Returns rows written.
"""
ids = [i for i in vuln_ids if i]
if not ids:
return 0
rows = (
db.query(Vulnerability)
.filter(Vulnerability.id.in_(ids))
.all()
)
written = 0
now = datetime.now()
for v in rows:
hostname = v.asset.hostname if v.asset else f"asset #{v.asset_id}"
desc = (
f"New finding detected: {v.cve_id} on {hostname} "
f"(severity={v.severity.value if hasattr(v.severity, 'value') else v.severity}, "
f"source={source})"
)
db.add(AuditLog(
user_id=None, # System/Auto
event_type=AuditEventType.VULNERABILITY_DETECTED,
event_description=desc[:500],
resource_type="vulnerability",
resource_id=str(v.id),
timestamp=now,
))
written += 1
if written:
logger.info("audit: %d VULNERABILITY_DETECTED events (%s)", written, source)
return written