Improved field-profile scoring.

This commit is contained in:
larssand
2026-06-22 21:37:32 +02:00
parent a7e23690da
commit 4e5f928405
3 changed files with 13 additions and 5 deletions

View File

@@ -2,6 +2,8 @@
SignalScope is a local multi-source security analytics agent. Its primary mode connects to Graylog through MCP, discovers the streams and fields already available in your environment, and uses stream profiles to normalize events, build baselines, correlate entities, and explain anomalies with a local LLM. SignalScope is a local multi-source security analytics agent. Its primary mode connects to Graylog through MCP, discovers the streams and fields already available in your environment, and uses stream profiles to normalize events, build baselines, correlate entities, and explain anomalies with a local LLM.
Its running only locally and if using LLM it's running also locally so no data is sent or exposed outside.
FortiGate is one supported example. The same workflow applies to DNS/AdGuard, Windows Event Logs, Sysmon, Nginx, Squid, VPN, Proxmox, Filebeat-collected logs, and future Graylog streams. FortiGate is one supported example. The same workflow applies to DNS/AdGuard, Windows Event Logs, Sysmon, Nginx, Squid, VPN, Proxmox, Filebeat-collected logs, and future Graylog streams.
The Python module and legacy `fgai` command remain available for compatibility. New installations can use `signalscope`. The Python module and legacy `fgai` command remain available for compatibility. New installations can use `signalscope`.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

View File

@@ -134,11 +134,17 @@ class BaselineStore:
rows = connection.execute("select events, numeric_sum from profile_buckets where stream_id=? and entity=? and field=? order by bucket_start desc limit 25", (stream, entity, field)).fetchall() rows = connection.execute("select events, numeric_sum from profile_buckets where stream_id=? and entity=? and field=? order by bucket_start desc limit 25", (stream, entity, field)).fetchall()
if len(rows) < 12: if len(rows) < 12:
continue continue
averages = [row[1] / row[0] if row[0] else 0 for row in rows] if values[1] == 0:
baseline = mean(averages) history = [row[0] for row in rows]
deviation = abs((values[1] / values[0] if values[0] else 0) - baseline) current_value = values[0]
if deviation > (pstdev(averages) or 1.0) * 3: reason = f"{field} event rate deviates from its stream baseline"
output[entity].append({"field": field, "stream_id": stream, "score": 15, "reason": f"{field} deviates from its stream baseline"}) else:
history = [row[1] / row[0] if row[0] else 0 for row in rows]
current_value = values[1] / values[0] if values[0] else 0
reason = f"{field} value deviates from its stream baseline"
deviation = abs(current_value - mean(history))
if deviation > (pstdev(history) or 1.0) * 3:
output[entity].append({"field": field, "stream_id": stream, "score": 15, "reason": reason})
return output return output
def profiles(self, source_ips: set[str]) -> dict[str, dict[str, object]]: def profiles(self, source_ips: set[str]) -> dict[str, dict[str, object]]: