Skip to content

Commit 9f17aff

Browse files
committed
fix: use WARNING prefix for monitoring-only alerts, handle all TCP states in lsof
- Network monitor alerts now use 'WARNING [rule_id]' instead of 'BLOCKED [rule_id]' to accurately reflect monitoring-only detection - Dashboard regex updated to match both BLOCKED and WARNING prefixes - lsof parser now handles all TCP states (SYN_SENT, CLOSE_WAIT, etc.) not just ESTABLISHED; skips LISTEN sockets - E2E test verified: connection to blocked IP-005 (144.76.217.73) detected and alert shown in dashboard
1 parent 74ffef7 commit 9f17aff

File tree

2 files changed

+13
-7
lines changed

2 files changed

+13
-7
lines changed

deploy/dashboard/app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
_BLOCK_LINE_RE = re.compile(
5757
r"(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}[,.]?\d*)\s+"
5858
r"WARNING\s+\[[\w.]+\]\s+"
59-
r"BLOCKED\s+\[([A-Z0-9_-]+)\]\s+(.*)"
59+
r"(?:BLOCKED|WARNING)\s+\[([A-Z0-9_-]+)\]\s+(.*)"
6060
)
6161

6262

deploy/macos/log_tailer.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -324,18 +324,24 @@ def monitor_network_connections() -> None:
324324
time.sleep(NET_POLL_INTERVAL)
325325
continue
326326

327-
# Parse lsof output for ESTABLISHED TCP connections
328-
# Format: COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
329-
# NAME looks like: 1.2.3.4:443->5.6.7.8:12345 (ESTABLISHED)
327+
# Parse lsof output for TCP connections (any state)
328+
# Format: COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME (STATE)
329+
# NAME looks like: 192.168.1.1:54321->104.22.10.63:443
330+
# STATE can be: (ESTABLISHED), (SYN_SENT), (CLOSE_WAIT), (LISTEN), etc.
330331
ip_re = re.compile(r"(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+)")
331332

332333
for line in lsof_out.stdout.splitlines():
333334
if "TCP" not in line:
334335
continue
336+
# Skip LISTEN sockets (incoming, not outbound connections)
337+
if "(LISTEN)" in line:
338+
continue
335339
parts = line.split()
336340
if len(parts) < 9:
337341
continue
338-
name = parts[-1] if parts[-1] != "(ESTABLISHED)" else parts[-2]
342+
# The connection info is the second-to-last field if state is present,
343+
# or the last field if no state. State is always in parens.
344+
name = parts[-2] if parts[-1].startswith("(") else parts[-1]
339345
# Extract remote IP from "local->remote" format
340346
if "->" in name:
341347
remote_part = name.split("->")[1]
@@ -357,7 +363,7 @@ def monitor_network_connections() -> None:
357363
if alert_key not in seen_alerts:
358364
seen_alerts.add(alert_key)
359365
block_logger.warning(
360-
"BLOCKED [%s] action=network-connect target=%s (IP match)",
366+
"WARNING [%s] action=network-connect target=%s (IP match, monitoring-only)",
361367
rid, remote_ip,
362368
)
363369
dispatch_alert_async(
@@ -380,7 +386,7 @@ def monitor_network_connections() -> None:
380386
if alert_key not in seen_alerts:
381387
seen_alerts.add(alert_key)
382388
block_logger.warning(
383-
"BLOCKED [%s] action=dns-lookup target=%s (resolved from %s)",
389+
"WARNING [%s] action=dns-lookup target=%s (resolved from %s, monitoring-only)",
384390
rid, blocked_domain, remote_ip,
385391
)
386392
dispatch_alert_async(

0 commit comments

Comments
 (0)