From be8a597be073de53149a0e60d96e854e74b5f535273a3231dce9e2ab6ee0efd4 Mon Sep 17 00:00:00 2001 From: larssand Date: Mon, 22 Jun 2026 19:32:18 +0200 Subject: [PATCH] add related findings --- src/fgai/correlation.py | 10 +++++++++- src/fgai/dashboard.py | 8 +++++++- tests/test_correlation.py | 1 + 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/fgai/correlation.py b/src/fgai/correlation.py index 33cfc48..28f9dff 100644 --- a/src/fgai/correlation.py +++ b/src/fgai/correlation.py @@ -17,5 +17,13 @@ def correlate_source_ips(events: list[LogEvent], *, limit: int = 20) -> list[dic if len(streams) < 2: continue threat_events = sum(event.action in THREAT_ACTIONS or is_utm_event(event) for event in source_events) - correlations.append({"source_ip": source_ip, "streams": streams, "events": len(source_events), "security_events": threat_events}) + samples = [ + { + "stream": event.fields.get("fgai_stream", "local_syslog"), "timestamp": event.fields.get("eventtime", event.fields.get("timestamp", "")), + "type": event.fields.get("type", ""), "subtype": event.subtype, "action": event.action, + "severity": event.severity, "destination": event.dst_ip or "", "service": event.fields.get("service", ""), + } + for event in source_events[:20] + ] + correlations.append({"source_ip": source_ip, "streams": streams, "events": len(source_events), "security_events": threat_events, "samples": samples}) return sorted(correlations, key=lambda item: (int(item["security_events"]), int(item["events"])), reverse=True)[:limit] diff --git a/src/fgai/dashboard.py b/src/fgai/dashboard.py index cb53e9d..2febba4 100644 --- a/src/fgai/dashboard.py +++ b/src/fgai/dashboard.py @@ -62,7 +62,7 @@ HTML = """

AI Assessment

LLM assessment disabled.

Anomalies

Recommendations

-

Block Candidates

Threat Intelligence

Policy Findings

+

Related Activity Across Sources

Block Candidates

Threat Intelligence

Policy Findings

Diagnostics

Runtime Configuration

@@ -139,6 +139,12 @@ async function refresh() { {label:'Reasons', render:r => esc((r.reasons || []).join('; '))} ]); const reputationRows = Object.entries(data.reputation || {}).map(([ip, intel]) => ({ip, ...intel})); + const relatedRows = (data.cross_source_correlations || []).flatMap(correlation => (correlation.samples || []).map(sample => ({source_ip: correlation.source_ip, ...sample}))); + document.getElementById('relatedActivity').innerHTML = table(relatedRows, [ + {label:'Source IP', key:'source_ip'}, {label:'Stream', key:'stream'}, {label:'Time', key:'timestamp'}, + {label:'Type', key:'type'}, {label:'Action', key:'action'}, {label:'Severity', key:'severity'}, + {label:'Destination', key:'destination'}, {label:'Service', key:'service'} + ]); document.getElementById('reputation').innerHTML = table(reputationRows, [ {label:'IP', key:'ip'}, {label:'Provider', key:'provider'}, diff --git a/tests/test_correlation.py b/tests/test_correlation.py index 9068ed6..72b7f4d 100644 --- a/tests/test_correlation.py +++ b/tests/test_correlation.py @@ -13,3 +13,4 @@ class CorrelationTests(unittest.TestCase): result = correlate_source_ips(events) self.assertEqual(result[0]["source_ip"], "10.0.0.5") self.assertEqual(result[0]["streams"], ["DNS", "Fortigate"]) + self.assertEqual(len(result[0]["samples"]), 2)