From f11def0af160d5e8e203bbf221588a8ed58b28bb Mon Sep 17 00:00:00 2001 From: anti Date: Sun, 10 May 2026 04:06:42 -0400 Subject: [PATCH] fix(collector): strip port from remote_addr before attacker identity resolution host:port in remote_addr was creating a distinct Attacker row per TCP connection instead of per IP. Split on the last ':' in parse_rfc5424; preserve the port as fields['remote_port'] so repeated source ports are retained as fingerprint signal in bounty payloads. --- decnet/collector/worker.py | 9 ++++++++- decnet/web/ingester.py | 2 ++ tests/collector/test_collector.py | 15 +++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/decnet/collector/worker.py b/decnet/collector/worker.py index 7c1f4e12..fceda6d8 100644 --- a/decnet/collector/worker.py +++ b/decnet/collector/worker.py @@ -472,7 +472,14 @@ def parse_rfc5424(line: str) -> Optional[dict[str, Any]]: attacker_ip = "Unknown" for fname in _IP_FIELDS: if fname in fields: - attacker_ip = fields[fname] + raw = fields[fname] + # remote_addr may be "host:port" — split so identity keys on IP only. + host, _, port = raw.rpartition(":") + if host and port.isdigit(): + attacker_ip = host.strip("[]") # handle [::1]:port IPv6 form + fields.setdefault("remote_port", port) + else: + attacker_ip = raw break # Fallback for plain `logger` callers that don't use SD params (notably diff --git a/decnet/web/ingester.py b/decnet/web/ingester.py index 33ef05ed..b7d51f1a 100644 --- a/decnet/web/ingester.py +++ b/decnet/web/ingester.py @@ -640,6 +640,7 @@ async def _extract_bounty( "protocol": _fields.get("proto") or _fields.get("protocol", "h1"), "method": _fields.get("method"), "path": _fields.get("path"), + "remote_port": _fields.get("remote_port"), }, }) @@ -656,6 +657,7 @@ async def _extract_bounty( "settings": _fields.get("settings"), "frame_order": _fields.get("frame_order"), "protocol": "h2" if _evt_type == "http2_settings" else "h3", + "remote_port": _fields.get("remote_port"), }, }) diff --git a/tests/collector/test_collector.py b/tests/collector/test_collector.py index 72396f1b..926a850c 100644 --- a/tests/collector/test_collector.py +++ b/tests/collector/test_collector.py @@ -156,6 +156,21 @@ class TestParseRfc5424: assert result["fields"]["src_ip"] == "1.2.3.4" assert "src" not in result["fields"] + def test_remote_addr_with_port_strips_port(self): + """remote_addr="1.2.3.4:40838" — attacker_ip must be the host only.""" + line = self._make_line('remote_addr="192.168.1.5:40838"') + result = parse_rfc5424(line) + assert result["attacker_ip"] == "192.168.1.5" + assert result["fields"]["remote_port"] == "40838" + + def test_remote_addr_plain_ip_no_port(self): + """remote_addr="1.2.3.4" without port — attacker_ip is the full value, + no remote_port key injected.""" + line = self._make_line('remote_addr="192.168.1.5"') + result = parse_rfc5424(line) + assert result["attacker_ip"] == "192.168.1.5" + assert "remote_port" not in result["fields"] + def test_parses_msg(self): line = self._make_line(msg="hello world") result = parse_rfc5424(line)