75 lines
2.1 KiB
Python
75 lines
2.1 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Any
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TuningDecision:
|
|
keep: bool
|
|
reason: str
|
|
|
|
|
|
class AlertTuner:
|
|
"""Small second-stage noise filter for the dashboard/incident database.
|
|
|
|
It intentionally does not replace Suricata threshold.config. The latter is
|
|
the right place for sensor-level suppressions and thresholds. This filter
|
|
is a final guardrail so low-priority or explicitly ignored alerts do not
|
|
flood SQLite and the UI.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
max_severity: int,
|
|
ignore_sids: str = "",
|
|
ignore_categories: str = "",
|
|
) -> None:
|
|
self.max_severity = max(0, int(max_severity))
|
|
self.ignore_sids = _parse_int_set(ignore_sids)
|
|
self.ignore_categories = {
|
|
value.strip().casefold()
|
|
for value in (ignore_categories or "").split(",")
|
|
if value.strip()
|
|
}
|
|
|
|
def evaluate(self, event: dict[str, Any]) -> TuningDecision:
|
|
alert = event.get("alert") or {}
|
|
sid = _as_int(alert.get("signature_id"))
|
|
severity = _as_int(alert.get("severity"))
|
|
category = str(alert.get("category") or "").strip()
|
|
|
|
if sid is not None and sid in self.ignore_sids:
|
|
return TuningDecision(False, "ignored_sid")
|
|
|
|
if category and category.casefold() in self.ignore_categories:
|
|
return TuningDecision(False, "ignored_category")
|
|
|
|
if self.max_severity > 0:
|
|
if severity is None:
|
|
return TuningDecision(False, "invalid_severity")
|
|
if severity > self.max_severity:
|
|
return TuningDecision(False, "low_priority")
|
|
|
|
return TuningDecision(True, "accepted")
|
|
|
|
|
|
def _parse_int_set(value: str) -> set[int]:
|
|
result: set[int] = set()
|
|
for item in (value or "").split(","):
|
|
item = item.strip()
|
|
if not item:
|
|
continue
|
|
try:
|
|
result.add(int(item))
|
|
except ValueError:
|
|
continue
|
|
return result
|
|
|
|
|
|
def _as_int(value: Any) -> int | None:
|
|
try:
|
|
return int(value)
|
|
except (TypeError, ValueError):
|
|
return None
|