Started the roadmap with incident grouping.
This commit is contained in:
17
src/fgai/incidents.py
Normal file
17
src/fgai/incidents.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from .models import AnomalyFinding
|
||||||
|
|
||||||
|
|
||||||
|
def build_incidents(anomalies: list[AnomalyFinding], field_deviations: dict[str, list[dict[str, object]]], correlations: list[dict[str, object]]) -> list[dict[str, object]]:
|
||||||
|
correlation_by_ip = {str(item.get("source_ip")): item for item in correlations}
|
||||||
|
incidents = []
|
||||||
|
for anomaly in anomalies:
|
||||||
|
fields = field_deviations.get(anomaly.subject, [])
|
||||||
|
correlation = correlation_by_ip.get(anomaly.subject)
|
||||||
|
evidence = [*anomaly.reasons, *[str(item.get("reason", "")) for item in fields]]
|
||||||
|
score = min(100, anomaly.score + min(15, sum(int(item.get("score", 0)) for item in fields)))
|
||||||
|
if correlation:
|
||||||
|
score = min(100, score + 10); evidence.append(f"observed across {len(correlation.get('streams', []))} streams")
|
||||||
|
incidents.append({"entity": anomaly.subject, "score": score, "severity": anomaly.severity, "evidence": evidence[:8], "field_deviations": len(fields), "correlated_streams": correlation.get("streams", []) if correlation else []})
|
||||||
|
return sorted(incidents, key=lambda item: int(item["score"]), reverse=True)
|
||||||
@@ -13,6 +13,7 @@ from .feedback import FeedbackStore
|
|||||||
from .graylog_mcp import GraylogMcpClient
|
from .graylog_mcp import GraylogMcpClient
|
||||||
from .graylog_source import GraylogStreamSource
|
from .graylog_source import GraylogStreamSource
|
||||||
from .history import HistoryStore
|
from .history import HistoryStore
|
||||||
|
from .incidents import build_incidents
|
||||||
from .llm import ollama_dashboard_assessment
|
from .llm import ollama_dashboard_assessment
|
||||||
from .logs import local_in_failures, read_events, summarize_events, top_field_values
|
from .logs import local_in_failures, read_events, summarize_events, top_field_values
|
||||||
from .mitigation import parse_allowlist, suggest_block_candidates
|
from .mitigation import parse_allowlist, suggest_block_candidates
|
||||||
@@ -90,6 +91,7 @@ def build_status(
|
|||||||
reputation = enrich_ips(intel_ips, limit=25, enabled=threat_enabled)
|
reputation = enrich_ips(intel_ips, limit=25, enabled=threat_enabled)
|
||||||
threat_intel_status = ThreatIntelClient(enabled=threat_enabled).status()
|
threat_intel_status = ThreatIntelClient(enabled=threat_enabled).status()
|
||||||
recommendations = build_recommendations(events, anomalies, reputation)
|
recommendations = build_recommendations(events, anomalies, reputation)
|
||||||
|
correlations = correlate_source_ips(events)
|
||||||
block_candidates = suggest_block_candidates(
|
block_candidates = suggest_block_candidates(
|
||||||
events,
|
events,
|
||||||
min_events=min_block_events,
|
min_events=min_block_events,
|
||||||
@@ -130,7 +132,8 @@ def build_status(
|
|||||||
"event_context": build_event_context(events),
|
"event_context": build_event_context(events),
|
||||||
"field_deviations": field_deviations,
|
"field_deviations": field_deviations,
|
||||||
"feedback": feedback,
|
"feedback": feedback,
|
||||||
"cross_source_correlations": correlate_source_ips(events),
|
"cross_source_correlations": correlations,
|
||||||
|
"incidents": build_incidents(anomalies, field_deviations, correlations),
|
||||||
"anomalies": [
|
"anomalies": [
|
||||||
{
|
{
|
||||||
"subject": finding.subject,
|
"subject": finding.subject,
|
||||||
|
|||||||
9
tests/test_incidents.py
Normal file
9
tests/test_incidents.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import unittest
|
||||||
|
from fgai.incidents import build_incidents
|
||||||
|
from fgai.models import AnomalyFinding
|
||||||
|
|
||||||
|
class IncidentTests(unittest.TestCase):
|
||||||
|
def test_merges_anomaly_and_correlation(self):
|
||||||
|
anomaly = AnomalyFinding("10.0.0.1", 60, "high", "high", ["burst"], {})
|
||||||
|
result = build_incidents([anomaly], {"10.0.0.1": [{"score": 15, "reason": "new domain"}]}, [{"source_ip": "10.0.0.1", "streams": ["DNS", "Firewall"]}])
|
||||||
|
self.assertEqual(result[0]["score"], 85)
|
||||||
Reference in New Issue
Block a user