From 13b4514276231f52079637289e368b772517bd39 Mon Sep 17 00:00:00 2001
From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com>
Date: Mon, 6 Apr 2026 12:21:38 +0000
Subject: [PATCH 1/4] Fix critical security vulnerabilities (command injection,
HTTP->HTTPS, path traversal)
Agent-Logs-Url: https://github.com/nexuspcs/ConnectivityMonitor/sessions/c1d88ff1-9347-43db-82bc-11b4a594c014
Co-authored-by: nexuspcs <69493073+nexuspcs@users.noreply.github.com>
---
macos/lib/config.sh | 51 +++++++++++++++++------
macos/lib/network.sh | 2 +-
macos/lib/state.sh | 3 +-
python/connectivity_monitor/web_server.py | 17 ++++++--
windows/ConnectivityDropMonitor.ps1 | 16 ++++++-
5 files changed, 69 insertions(+), 20 deletions(-)
diff --git a/macos/lib/config.sh b/macos/lib/config.sh
index dd739af..1f63411 100755
--- a/macos/lib/config.sh
+++ b/macos/lib/config.sh
@@ -34,7 +34,7 @@ check_date_roll() {
# ================================================================
load_config() {
if [[ -f "$CM_CONFIG_PATH" ]]; then
- if python3 -c "import json; json.load(open('$CM_CONFIG_PATH'))" 2>/dev/null; then
+ if python3 -c "import json; json.load(open(\"${CM_CONFIG_PATH}\"))" 2>/dev/null; then
return 0
fi
fi
@@ -43,27 +43,52 @@ load_config() {
config_get() {
local key="$1"
- python3 -c "import json; d=json.load(open('${CM_CONFIG_PATH}')); print(d.get('${key}',''))" 2>/dev/null
+ # Use printf to safely pass key to Python, avoiding code injection
+ python3 -c "
+import json
+import sys
+key = sys.argv[1]
+with open(\"${CM_CONFIG_PATH}\", 'r') as f:
+ d = json.load(f)
+ print(d.get(key, ''))
+" "$key" 2>/dev/null
}
save_config() {
local cfg_adapter="$1" cfg_poll="$2" cfg_threshold="$3" cfg_targets="$4"
local cfg_latwarn="$5" cfg_enabledns="$6" cfg_dnstarget="$7" cfg_enablebeep="$8"
- python3 -c "
+ # Use Python script file to avoid shell injection vulnerabilities
+ python3 - "$cfg_adapter" "$cfg_poll" "$cfg_threshold" "$cfg_targets" \
+ "$cfg_latwarn" "$cfg_enabledns" "$cfg_dnstarget" "$cfg_enablebeep" \
+ "${CM_CONFIG_PATH}" <<'PYTHON_SCRIPT'
import json
+import sys
+
+# Read arguments from command line
+cfg_adapter = sys.argv[1]
+cfg_poll = int(sys.argv[2])
+cfg_threshold = int(sys.argv[3])
+cfg_targets = sys.argv[4]
+cfg_latwarn = int(sys.argv[5])
+cfg_enabledns = sys.argv[6].lower() in ('1', 'true')
+cfg_dnstarget = sys.argv[7]
+cfg_enablebeep = sys.argv[8].lower() in ('1', 'true')
+config_path = sys.argv[9]
+
d = {
- 'adapter': '${cfg_adapter}',
- 'poll': ${cfg_poll},
- 'threshold': ${cfg_threshold},
- 'targets': '${cfg_targets}',
- 'latWarn': ${cfg_latwarn},
- 'enableDns': bool(${cfg_enabledns}),
- 'dnsTarget': '${cfg_dnstarget}',
- 'enableBeep': bool(${cfg_enablebeep})
+ 'adapter': cfg_adapter,
+ 'poll': cfg_poll,
+ 'threshold': cfg_threshold,
+ 'targets': cfg_targets,
+ 'latWarn': cfg_latwarn,
+ 'enableDns': cfg_enabledns,
+ 'dnsTarget': cfg_dnstarget,
+ 'enableBeep': cfg_enablebeep
}
-with open('${CM_CONFIG_PATH}', 'w') as f:
+
+with open(config_path, 'w') as f:
json.dump(d, f, indent=2)
-" 2>/dev/null
+PYTHON_SCRIPT
}
# ================================================================
diff --git a/macos/lib/network.sh b/macos/lib/network.sh
index 99e2872..4b1a19c 100755
--- a/macos/lib/network.sh
+++ b/macos/lib/network.sh
@@ -78,7 +78,7 @@ get_local_ip() {
# ================================================================
detect_public_ip() {
local resp
- resp=$(curl -s --connect-timeout 5 --max-time 8 "http://ip-api.com/json" 2>/dev/null)
+ resp=$(curl -s --connect-timeout 5 --max-time 8 "https://ip-api.com/json" 2>/dev/null)
if [[ -n "$resp" ]]; then
public_ip=$(echo "$resp" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('query','N/A'))" 2>/dev/null)
isp_name=$(echo "$resp" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('isp','N/A'))" 2>/dev/null)
diff --git a/macos/lib/state.sh b/macos/lib/state.sh
index 6b19172..b875e6f 100755
--- a/macos/lib/state.sh
+++ b/macos/lib/state.sh
@@ -64,7 +64,8 @@ last_wifi_sig=""
last_trace_time=0
trace_pid=""
trace_target=""
-trace_outfile="/tmp/cm_traceroute_$$.txt"
+# Use mktemp for secure temporary file creation
+trace_outfile=$(mktemp 2>/dev/null) || trace_outfile="/tmp/cm_traceroute_$$_${RANDOM}.txt"
rr=0
enable_beep=0
diff --git a/python/connectivity_monitor/web_server.py b/python/connectivity_monitor/web_server.py
index 55b1822..e25da1d 100644
--- a/python/connectivity_monitor/web_server.py
+++ b/python/connectivity_monitor/web_server.py
@@ -1,6 +1,7 @@
"""Built-in HTTP web server — live dashboard, JSON API, file serving."""
import datetime
+import html
import json
import os
import threading
@@ -154,6 +155,14 @@ def _serve_file(self, base_dir, filename, content_type):
# Sanitize filename to prevent path traversal
safe_name = os.path.basename(filename)
filepath = os.path.join(base_dir, safe_name)
+
+ # Verify the resolved path is still within the base directory
+ real_base = os.path.realpath(base_dir)
+ real_file = os.path.realpath(filepath)
+ if not real_file.startswith(real_base + os.sep):
+ self._send(403, "text/plain", "Forbidden")
+ return
+
if os.path.isfile(filepath):
with open(filepath, "rb") as f:
self._send(200, content_type, f.read())
@@ -167,13 +176,15 @@ def _serve_file_list(self, directory, title, url_prefix):
files = sorted(os.listdir(directory), reverse=True)
items = ""
for f in files:
+ # HTML-escape filenames to prevent XSS
+ safe_f = html.escape(f)
items += '
{}\n'.format(
- url_prefix, f, f
+ url_prefix, safe_f, safe_f
)
if not items:
items = "No files yet"
- html = _file_list_html(title, items)
- self._send(200, "text/html", html)
+ html_content = _file_list_html(title, items)
+ self._send(200, "text/html", html_content)
def _serve_dashboard(self):
"""Serve the live auto-refreshing dashboard."""
diff --git a/windows/ConnectivityDropMonitor.ps1 b/windows/ConnectivityDropMonitor.ps1
index 8c9efdd..9ec7b5c 100644
--- a/windows/ConnectivityDropMonitor.ps1
+++ b/windows/ConnectivityDropMonitor.ps1
@@ -195,7 +195,7 @@ function GetLocalIP($alias) {
function DetectPublicIP {
try {
- $resp = Invoke-RestMethod -Uri "http://ip-api.com/json" -TimeoutSec 5 -ErrorAction Stop
+ $resp = Invoke-RestMethod -Uri "https://ip-api.com/json" -TimeoutSec 5 -ErrorAction Stop
$script:publicIP = $resp.query
$script:ispName = $resp.isp
}
@@ -225,7 +225,19 @@ function StartTraceroute($target) {
$script:lastTraceTime = $now
$script:traceJob = Start-Job -ScriptBlock {
param($t)
- $output = cmd /c "tracert -d -w 500 -h 10 $t" 2>&1
+ # Use ProcessStartInfo to avoid command injection vulnerabilities
+ $psi = New-Object System.Diagnostics.ProcessStartInfo
+ $psi.FileName = "tracert.exe"
+ $psi.Arguments = "-d -w 500 -h 10 $t"
+ $psi.RedirectStandardOutput = $true
+ $psi.RedirectStandardError = $true
+ $psi.UseShellExecute = $false
+ $psi.CreateNoWindow = $true
+ $proc = New-Object System.Diagnostics.Process
+ $proc.StartInfo = $psi
+ $proc.Start() | Out-Null
+ $output = $proc.StandardOutput.ReadToEnd()
+ $proc.WaitForExit()
return $output
} -ArgumentList $target
}
From 25b672a8a9fecad351f77adfbcd12f01c2e27daa Mon Sep 17 00:00:00 2001
From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com>
Date: Mon, 6 Apr 2026 12:23:13 +0000
Subject: [PATCH 2/4] Fix code quality issues - improve exception handling, add
input validation, use context managers
Agent-Logs-Url: https://github.com/nexuspcs/ConnectivityMonitor/sessions/c1d88ff1-9347-43db-82bc-11b4a594c014
Co-authored-by: nexuspcs <69493073+nexuspcs@users.noreply.github.com>
---
python/connectivity_monitor/__main__.py | 6 ++
python/connectivity_monitor/config.py | 120 +++++++++++++++++++++---
python/connectivity_monitor/network.py | 25 +++--
3 files changed, 126 insertions(+), 25 deletions(-)
diff --git a/python/connectivity_monitor/__main__.py b/python/connectivity_monitor/__main__.py
index ce620a1..ef711f6 100644
--- a/python/connectivity_monitor/__main__.py
+++ b/python/connectivity_monitor/__main__.py
@@ -3,6 +3,12 @@
import argparse
import sys
+# Check Python version before importing other modules
+if sys.version_info < (3, 6):
+ sys.exit("Error: Python 3.6+ is required. You are using Python {}.{}.{}".format(
+ sys.version_info.major, sys.version_info.minor, sys.version_info.micro
+ ))
+
from .config import load_config, interactive_setup, headless_config, ensure_dirs
from .monitor import run_monitor
diff --git a/python/connectivity_monitor/config.py b/python/connectivity_monitor/config.py
index c6b5aed..92b2c82 100644
--- a/python/connectivity_monitor/config.py
+++ b/python/connectivity_monitor/config.py
@@ -2,6 +2,7 @@
import json
import os
+import re
import sys
@@ -19,6 +20,10 @@
def get_base_dir():
"""Return base directory for logs/reports/config."""
+ # Check environment variable first for user customization
+ base = os.getenv("CM_BASE_DIR")
+ if base:
+ return os.path.expanduser(base)
home = os.path.expanduser("~")
return os.path.join(home, "ConnectivityMonitor")
@@ -44,7 +49,9 @@ def load_config():
try:
with open(path, "r") as f:
return json.load(f)
- except Exception:
+ except (json.JSONDecodeError, IOError, OSError) as e:
+ # Invalid JSON or file read errors
+ print(f"Warning: Could not load config from {path}: {e}", file=sys.stderr)
return None
return None
@@ -71,6 +78,36 @@ def prompt_yes_no(prompt, default="Y"):
return val.upper().startswith("Y")
+def validate_hostname(hostname):
+ """Validate hostname format (basic check)."""
+ if not hostname or len(hostname) > 253:
+ return False
+ # Basic hostname regex: alphanumeric, hyphens, dots
+ pattern = r'^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$'
+ return bool(re.match(pattern, hostname))
+
+
+def validate_targets(targets):
+ """Validate comma-separated ping targets."""
+ import ipaddress
+ if not targets:
+ return False
+ target_list = [t.strip() for t in targets.split(",")]
+ if not target_list:
+ return False
+ for target in target_list:
+ # Try to parse as IP address first
+ try:
+ ipaddress.ip_address(target)
+ continue
+ except ValueError:
+ pass
+ # Otherwise validate as hostname
+ if not validate_hostname(target):
+ return False
+ return True
+
+
def interactive_setup(saved_cfg=None):
"""Run interactive configuration and return config dict."""
print()
@@ -97,24 +134,77 @@ def interactive_setup(saved_cfg=None):
return cfg
cfg = dict(DEFAULTS)
- poll = prompt_default(" Poll interval (seconds)", cfg["poll"])
- cfg["poll"] = int(poll)
-
- threshold = prompt_default(" Failure threshold for drop", cfg["threshold"])
- cfg["threshold"] = int(threshold)
-
- targets = prompt_default(" Ping targets (comma-sep)", cfg["targets"])
- cfg["targets"] = targets
- lat_warn = prompt_default(" Latency warning (ms)", cfg["lat_warn"])
- cfg["lat_warn"] = int(lat_warn)
+ # Poll interval validation
+ while True:
+ try:
+ poll = prompt_default(" Poll interval (seconds)", cfg["poll"])
+ poll_val = float(poll)
+ if 0.1 <= poll_val <= 3600:
+ cfg["poll"] = poll_val
+ break
+ else:
+ print(" Error: Poll interval must be between 0.1 and 3600 seconds")
+ except ValueError:
+ print(" Error: Please enter a valid number")
+
+ # Failure threshold validation
+ while True:
+ try:
+ threshold = prompt_default(" Failure threshold for drop", cfg["threshold"])
+ threshold_val = int(threshold)
+ if 1 <= threshold_val <= 100:
+ cfg["threshold"] = threshold_val
+ break
+ else:
+ print(" Error: Threshold must be between 1 and 100")
+ except ValueError:
+ print(" Error: Please enter a valid integer")
+
+ # Ping targets validation
+ while True:
+ targets = prompt_default(" Ping targets (comma-sep)", cfg["targets"])
+ if validate_targets(targets):
+ cfg["targets"] = targets
+ break
+ else:
+ print(" Error: Invalid target format. Use IP addresses or hostnames (comma-separated)")
+
+ # Latency warning validation
+ while True:
+ try:
+ lat_warn = prompt_default(" Latency warning (ms)", cfg["lat_warn"])
+ lat_val = int(lat_warn)
+ if 1 <= lat_val <= 10000:
+ cfg["lat_warn"] = lat_val
+ break
+ else:
+ print(" Error: Latency warning must be between 1 and 10000 ms")
+ except ValueError:
+ print(" Error: Please enter a valid integer")
cfg["enable_dns"] = prompt_yes_no(" Enable DNS health check?", "Y")
if cfg["enable_dns"]:
- cfg["dns_target"] = prompt_default(" DNS test hostname", cfg["dns_target"])
-
- web_port = prompt_default(" Web dashboard port", cfg["web_port"])
- cfg["web_port"] = int(web_port)
+ while True:
+ dns_target = prompt_default(" DNS test hostname", cfg["dns_target"])
+ if validate_hostname(dns_target):
+ cfg["dns_target"] = dns_target
+ break
+ else:
+ print(" Error: Invalid hostname format")
+
+ # Web port validation
+ while True:
+ try:
+ web_port = prompt_default(" Web dashboard port", cfg["web_port"])
+ port_val = int(web_port)
+ if 1 <= port_val <= 65535:
+ cfg["web_port"] = port_val
+ break
+ else:
+ print(" Error: Port must be between 1 and 65535")
+ except ValueError:
+ print(" Error: Please enter a valid port number")
save_config(cfg)
print(" Config saved to {}".format(get_config_path()))
diff --git a/python/connectivity_monitor/network.py b/python/connectivity_monitor/network.py
index 2395455..196b0e2 100644
--- a/python/connectivity_monitor/network.py
+++ b/python/connectivity_monitor/network.py
@@ -40,7 +40,8 @@ def ping_test(target, timeout=2):
# Got response but couldn't parse latency — still ok
return {"ok": True, "lat": 0, "target": target, "time": ts}
return {"ok": False, "lat": None, "target": target, "time": ts}
- except Exception:
+ except (subprocess.TimeoutExpired, subprocess.SubprocessError, OSError, ValueError) as e:
+ # Network errors, command failures, or parsing errors
return {"ok": False, "lat": None, "target": target, "time": ts}
@@ -51,7 +52,8 @@ def dns_test(hostname):
socket.getaddrinfo(hostname, None)
elapsed = (time.monotonic() - start) * 1000
return {"ok": True, "ms": round(elapsed, 1)}
- except Exception:
+ except (socket.gaierror, socket.timeout, OSError) as e:
+ # DNS resolution failures
return {"ok": False, "ms": None}
@@ -82,7 +84,8 @@ def detect_gateway():
m = re.search(r"Default Gateway.*?:\s*([\d.]+)", out.stdout)
if m:
return m.group(1)
- except Exception:
+ except (subprocess.TimeoutExpired, subprocess.SubprocessError, OSError) as e:
+ # Command execution failures
pass
return "N/A"
@@ -90,12 +93,13 @@ def detect_gateway():
def get_local_ip():
"""Get local IP address of the machine."""
try:
- s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- s.connect(("8.8.8.8", 80))
- ip = s.getsockname()[0]
- s.close()
- return ip
- except Exception:
+ # Use context manager for automatic socket cleanup
+ with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
+ s.connect(("8.8.8.8", 80))
+ ip = s.getsockname()[0]
+ return ip
+ except (socket.error, OSError) as e:
+ # Network/socket errors
return "N/A"
@@ -112,7 +116,8 @@ def detect_public_ip(state):
data = json_mod.loads(resp.read().decode())
state.public_ip = data.get("query", "N/A")
state.isp_name = data.get("isp", "N/A")
- except Exception:
+ except (urllib.error.URLError, urllib.error.HTTPError, OSError, ValueError) as e:
+ # Network errors or JSON parsing failures
state.public_ip = "N/A"
state.isp_name = "N/A"
From 80fedb33db14ba41e2a45b34906f27d0e5135525 Mon Sep 17 00:00:00 2001
From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com>
Date: Mon, 6 Apr 2026 12:24:46 +0000
Subject: [PATCH 3/4] Add comprehensive documentation - module docstrings,
function docs, variable comments
Agent-Logs-Url: https://github.com/nexuspcs/ConnectivityMonitor/sessions/c1d88ff1-9347-43db-82bc-11b4a594c014
Co-authored-by: nexuspcs <69493073+nexuspcs@users.noreply.github.com>
---
macos/lib/state.sh | 137 +++++++++++++++++-------
python/connectivity_monitor/__init__.py | 35 ++++++
python/connectivity_monitor/metrics.py | 115 ++++++++++++++++++--
3 files changed, 240 insertions(+), 47 deletions(-)
diff --git a/macos/lib/state.sh b/macos/lib/state.sh
index b875e6f..11ff809 100755
--- a/macos/lib/state.sh
+++ b/macos/lib/state.sh
@@ -2,80 +2,143 @@
# ============================================================================
# STATE MODULE - Global state variables
# Connectivity Monitor v4.0 (macOS)
+#
+# This module defines all global variables used throughout the monitoring
+# session. Variables are organized into logical groups for clarity.
+#
+# IMPORTANT: This file must be sourced before all other library modules.
+#
+# Global Variable Groups:
+# - History arrays: Store ping results over time (circular buffer, max 1000)
+# - Drop tracking: Record connectivity drop events
+# - Per-target stats: Statistics per ping target (associative arrays)
+# - Gateway tracking: Monitor gateway latency separately
+# - Baseline learning: Learn normal latency from first 30 pings
+# - Hourly heatmap: Aggregate latency by hour of day
+# - Traceroute data: Store recent traceroute results
+# - Session state: Counters, flags, timestamps
+# - Directory paths: Base directory, logs, reports, config
# ============================================================================
-# History arrays (parallel arrays for timestamp, latency, target)
+# ============================================================================
+# HISTORY ARRAYS - Recent ping results (circular buffer)
+# ============================================================================
+# Parallel arrays storing ping history (max 1000 entries):
+# - hist_times: Unix timestamps of ping attempts
+# - hist_lats: Latency values in ms (empty string for failed pings)
+# - hist_targets: Target IP/hostname for each ping
declare -a hist_times=()
declare -a hist_lats=()
declare -a hist_targets=()
-# Drop arrays
+# ============================================================================
+# DROP TRACKING - Connectivity outage events
+# ============================================================================
+# Parallel arrays storing drop events:
+# - drop_starts: ISO 8601 timestamp when drop started
+# - drop_ends: ISO 8601 timestamp when connectivity restored
+# - drop_durations: Duration in seconds
+# - drop_targets: Target being monitored when drop occurred
+# - drop_diagnoses: Diagnostic message (e.g., "Gateway unreachable")
declare -a drop_starts=()
declare -a drop_ends=()
declare -a drop_durations=()
declare -a drop_targets=()
declare -a drop_diagnoses=()
-# Per-target stats (associative arrays)
+# ============================================================================
+# PER-TARGET STATISTICS - Associative arrays keyed by target IP/hostname
+# ============================================================================
+# Track statistics for each ping target independently:
+# - per_target_sent: Number of pings sent to this target
+# - per_target_ok: Number of successful pings
+# - per_target_lats: Comma-separated latency values
declare -A per_target_sent=()
declare -A per_target_ok=()
declare -A per_target_lats=()
-# Gateway history
+# ============================================================================
+# GATEWAY TRACKING
+# ============================================================================
+# Gateway latency history (separate from main ping targets)
declare -a gw_lats=()
-# Threshold breach arrays
+# ============================================================================
+# LATENCY THRESHOLD BREACH TRACKING
+# ============================================================================
+# Track periods when latency exceeds configured threshold:
+# - breach_starts: ISO timestamp when breach started
+# - breach_ends: ISO timestamp when latency returned to normal
+# - breach_durations: Duration in seconds
+# - breach_avglats: Average latency during breach period
declare -a breach_starts=()
declare -a breach_ends=()
declare -a breach_durations=()
declare -a breach_avglats=()
-# Baseline learning
+# ============================================================================
+# BASELINE LEARNING
+# ============================================================================
+# Store first 30 successful ping latencies to establish baseline
declare -a baseline_samples=()
-# Hourly data (associative: hour -> comma-separated latencies)
+# ============================================================================
+# HOURLY HEATMAP DATA
+# ============================================================================
+# Associative array: hour (0-23) -> comma-separated latencies
+# Used to generate 24-hour heatmap showing time-of-day patterns
declare -A hourly_data=()
-# Traceroute entries (each entry is "target|time|hop_data")
+# ============================================================================
+# TRACEROUTE DATA
+# ============================================================================
+# Array of recent traceroute results (max 3 entries)
+# Each entry format: "target|timestamp|hop_data"
declare -a trace_entries=()
-# Counters and flags
-fail_count=0
-is_down=0
-down_start=""
-session_start=$(date +%s)
-total_pings=0
-total_success=0
-shutdown_flag=0
-max_history=1000
-last_line_count=0
-paused=0
-baseline_latency=""
-baseline_locked=0
-public_ip="detecting..."
-isp_name="detecting..."
-active_tab=1
-breach_active=0
-breach_start=""
-current_date=$(date +%Y-%m-%d)
-last_diagnosis_msg="Initializing..."
-last_diagnosis_color="90"
-last_wifi_sig=""
-last_trace_time=0
-trace_pid=""
-trace_target=""
+# ============================================================================
+# SESSION STATE - Counters and flags
+# ============================================================================
+fail_count=0 # Consecutive failed pings
+is_down=0 # Boolean: currently in a drop state (0=up, 1=down)
+down_start="" # ISO timestamp when current drop started
+session_start=$(date +%s) # Unix timestamp of session start
+total_pings=0 # Total ping attempts this session
+total_success=0 # Total successful pings this session
+shutdown_flag=0 # Boolean: user requested shutdown (0=run, 1=exit)
+max_history=1000 # Maximum ping history entries (circular buffer)
+last_line_count=0 # Terminal line count for display updates
+paused=0 # Boolean: monitoring paused (0=running, 1=paused)
+baseline_latency="" # Calculated baseline latency (ms) after 30 samples
+baseline_locked=0 # Boolean: baseline calculation complete
+public_ip="detecting..." # Public IP address (from ip-api.com)
+isp_name="detecting..." # ISP name (from ip-api.com)
+active_tab=1 # Currently displayed dashboard tab (1-5)
+breach_active=0 # Boolean: currently in latency threshold breach
+breach_start="" # ISO timestamp of current breach start
+current_date=$(date +%Y-%m-%d) # Current date for log rotation detection
+last_diagnosis_msg="Initializing..." # Last diagnostic message
+last_diagnosis_color="90" # ANSI color code for last diagnosis
+last_wifi_sig="" # Last WiFi signal strength percentage
+last_trace_time=0 # Unix timestamp of last traceroute invocation
+trace_pid="" # PID of background traceroute process
+trace_target="" # Target for current/last traceroute
# Use mktemp for secure temporary file creation
trace_outfile=$(mktemp 2>/dev/null) || trace_outfile="/tmp/cm_traceroute_$$_${RANDOM}.txt"
-rr=0
-enable_beep=0
+rr=0 # Round-robin index for target rotation
+enable_beep=0 # Boolean: audible beep on drops (0=off, 1=on)
-# Directory setup
+# ============================================================================
+# DIRECTORY PATHS
+# ============================================================================
CM_BASE_DIR="$HOME/Documents/ConnectivityMonitor"
CM_LOGS_DIR="$CM_BASE_DIR/logs"
CM_REPORTS_DIR="$CM_BASE_DIR/reports"
CM_CONFIG_PATH="$CM_BASE_DIR/monitor_config.json"
-# Log file paths (set by get_daily_log_paths)
+# ============================================================================
+# LOG FILE PATHS (set by get_daily_log_paths function)
+# ============================================================================
ping_log_file=""
drop_log_file=""
breach_log_file=""
diff --git a/python/connectivity_monitor/__init__.py b/python/connectivity_monitor/__init__.py
index d9a4773..65ceff0 100644
--- a/python/connectivity_monitor/__init__.py
+++ b/python/connectivity_monitor/__init__.py
@@ -4,5 +4,40 @@
# Works on Linux, macOS, Raspberry Pi — Python 3.6+ stdlib only
# JAMES COATES and Claude Opus 4.6 (Graciously hosted with GitHub CoPilot)
# ============================================================================
+"""
+Connectivity Monitor — Real-time network monitoring with latency tracking
+
+A cross-platform network monitoring tool that continuously pings targets,
+tracks latency and packet loss, detects outages, and provides live analytics
+through a web dashboard.
+
+Key Features:
+- Continuous ICMP ping monitoring with configurable targets
+- Live web dashboard with auto-refresh (5 tabs: Overview, Graph, Drops, Targets, Heatmap)
+- Smart diagnostics (local network, gateway, ISP, DNS health checks)
+- Health scoring (A+ to F based on latency, jitter, packet loss)
+- Baseline learning (learns normal latency from first 30 pings)
+- Automatic traceroute on connectivity drops
+- CSV logging with daily rotation (ping results, drops, latency breaches)
+- HTML report generation with Chart.js visualizations
+- Hourly heatmap for time-of-day pattern analysis
+
+Modules:
+- config: Configuration management (JSON file, CLI prompts, validation)
+- state: Global monitor state (all mutable session data)
+- network: Network utilities (ping, DNS, gateway detection, traceroute)
+- metrics: Statistical calculations (loss, avg, percentile, jitter, health score)
+- csv_logger: CSV file logging with daily rotation
+- html_report: HTML report generation with Chart.js
+- web_server: Built-in HTTP server for live dashboard and JSON API
+- monitor: Main monitoring loop and orchestration
+
+Usage:
+ python -m connectivity_monitor # Interactive setup
+ python -m connectivity_monitor --headless # Headless mode with saved config
+ python -m connectivity_monitor --targets 1.1.1.1,8.8.8.8 --web-port 9090
+
+See README.md and docs/ directory for full documentation.
+"""
__version__ = "4.0"
diff --git a/python/connectivity_monitor/metrics.py b/python/connectivity_monitor/metrics.py
index 0ba42ca..f108d28 100644
--- a/python/connectivity_monitor/metrics.py
+++ b/python/connectivity_monitor/metrics.py
@@ -1,15 +1,42 @@
-"""Metrics engine — loss, avg, min, max, percentile, jitter, health, trend."""
+"""Metrics engine — loss, avg, min, max, percentile, jitter, health, trend.
+
+This module provides statistical analysis of network performance data.
+All functions operate on a MonitorState object which contains ping history.
+
+Key Functions:
+- loss(): Calculate packet loss percentage
+- avg(), min_lat(), max_lat(): Latency statistics
+- percentile(): Calculate percentile latency (e.g., p95, p99)
+- jitter(): Average latency variation between consecutive pings
+- uptime(): Percentage of successful pings
+- get_health_score(): Overall connection health (0-100) with letter grade
+- get_trend(): Trend analysis comparing recent vs baseline performance
+"""
import math
def get_latency_values(state):
- """Extract non-None latency values from history."""
+ """Extract non-None latency values from history.
+
+ Args:
+ state: MonitorState object with ping history
+
+ Returns:
+ List of float latency values (excludes failed pings)
+ """
return [h["latency"] for h in state.history if h.get("latency") is not None]
def loss(state):
- """Packet loss percentage."""
+ """Calculate packet loss percentage.
+
+ Args:
+ state: MonitorState object
+
+ Returns:
+ float: Packet loss percentage (0-100), rounded to 1 decimal place
+ """
total = len(state.history)
if total == 0:
return 0
@@ -18,7 +45,14 @@ def loss(state):
def avg(state):
- """Average latency of successful pings."""
+ """Calculate average latency of successful pings.
+
+ Args:
+ state: MonitorState object
+
+ Returns:
+ float: Average latency in milliseconds, rounded to 1 decimal place
+ """
vals = get_latency_values(state)
if not vals:
return 0
@@ -26,7 +60,14 @@ def avg(state):
def min_lat(state):
- """Minimum latency."""
+ """Calculate minimum latency.
+
+ Args:
+ state: MonitorState object
+
+ Returns:
+ float: Minimum latency in milliseconds, rounded to 1 decimal place
+ """
vals = get_latency_values(state)
if not vals:
return 0
@@ -34,7 +75,14 @@ def min_lat(state):
def max_lat(state):
- """Maximum latency."""
+ """Calculate maximum latency.
+
+ Args:
+ state: MonitorState object
+
+ Returns:
+ float: Maximum latency in milliseconds, rounded to 1 decimal place
+ """
vals = get_latency_values(state)
if not vals:
return 0
@@ -42,7 +90,15 @@ def max_lat(state):
def percentile(state, pct):
- """Percentile latency (0-100)."""
+ """Calculate percentile latency (e.g., 95th percentile).
+
+ Args:
+ state: MonitorState object
+ pct: Percentile value (0-100)
+
+ Returns:
+ float: Latency at the specified percentile, rounded to 1 decimal place
+ """
vals = sorted(get_latency_values(state))
if not vals:
return 0
@@ -53,7 +109,17 @@ def percentile(state, pct):
def jitter(state):
- """Average latency variation between consecutive pings."""
+ """Calculate average latency variation between consecutive pings.
+
+ Jitter is the average absolute difference between consecutive latency measurements.
+ High jitter indicates unstable network performance.
+
+ Args:
+ state: MonitorState object
+
+ Returns:
+ float: Average jitter in milliseconds, rounded to 1 decimal place
+ """
vals = get_latency_values(state)
if len(vals) < 2:
return 0
@@ -62,7 +128,14 @@ def jitter(state):
def uptime(state):
- """Percentage of successful pings."""
+ """Calculate percentage of successful pings.
+
+ Args:
+ state: MonitorState object
+
+ Returns:
+ float: Uptime percentage (0-100), rounded to 2 decimal places
+ """
total = len(state.history)
if total == 0:
return 100.0
@@ -71,7 +144,29 @@ def uptime(state):
def get_health_score(state):
- """Health score 0-100 with letter grade. Returns {score, grade, color}."""
+ """Calculate overall connection health score with letter grade.
+
+ Health score formula:
+ - Start at 100
+ - Subtract 3 points per 1% packet loss
+ - Subtract points for high average latency (scaled)
+ - Subtract points for high jitter (scaled)
+
+ Grading:
+ - A+: 90-100 (excellent)
+ - A: 80-89 (great)
+ - B: 70-79 (good)
+ - C: 60-69 (fair)
+ - D: 50-59 (poor)
+ - F: 0-49 (failing)
+
+ Args:
+ state: MonitorState object
+
+ Returns:
+ dict: {'score': int (0-100), 'grade': str, 'color': str}
+ where color is suitable for terminal/web display
+ """
l = loss(state)
a = avg(state)
j = jitter(state)
From 74abab02c63d61eb12b3fd9ff74ee89ba4615fab Mon Sep 17 00:00:00 2001
From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com>
Date: Mon, 6 Apr 2026 12:26:28 +0000
Subject: [PATCH 4/4] Add SECURITY.md with comprehensive security guidelines
and best practices
Agent-Logs-Url: https://github.com/nexuspcs/ConnectivityMonitor/sessions/c1d88ff1-9347-43db-82bc-11b4a594c014
Co-authored-by: nexuspcs <69493073+nexuspcs@users.noreply.github.com>
---
SECURITY.md | 272 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 272 insertions(+)
create mode 100644 SECURITY.md
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..ab54a1e
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,272 @@
+# Security Policy
+
+## Overview
+
+ConnectivityMonitor is designed for local network monitoring and diagnostics. This document outlines security considerations, best practices, and how to report security vulnerabilities.
+
+## Security Model
+
+ConnectivityMonitor is designed to run on:
+- **Local machines** for personal network diagnostics
+- **Private networks** behind firewalls
+- **Trusted environments** where users have appropriate access
+
+### Threat Model
+
+**What ConnectivityMonitor protects against:**
+- Command injection in network operations
+- Path traversal in file serving
+- Code injection via configuration files
+- Insecure temporary file creation
+
+**What ConnectivityMonitor does NOT protect against:**
+- Unauthorized access to the web dashboard (no authentication)
+- Data exfiltration if the host is compromised
+- Network-level attacks on the monitoring traffic itself
+
+## Security Best Practices
+
+### Web Dashboard Security
+
+The built-in web server (Python version) **does not include authentication** by default. Follow these guidelines:
+
+#### For Local Use Only (Recommended)
+```bash
+# Bind to localhost only (default behavior can be changed)
+python -m connectivity_monitor --web-port 8080
+# Access via http://localhost:8080 only
+```
+
+#### For Network Access
+If you need to access the dashboard from other devices on your network:
+
+1. **Firewall Configuration**: Ensure your firewall only allows connections from trusted IPs
+2. **Bind Address**: By default, the server binds to `0.0.0.0` (all interfaces)
+3. **Network Isolation**: Run only on trusted private networks (home/office LAN)
+4. **VPN Access**: For remote access, use a VPN instead of exposing the port publicly
+
+**⚠️ WARNING**: Do NOT expose the web dashboard to the public internet without additional security measures.
+
+### Configuration File Security
+
+Configuration files are stored at:
+- **Linux/macOS**: `~/ConnectivityMonitor/monitor_config.json`
+- **Windows**: `%USERPROFILE%\ConnectivityMonitor\monitor_config.json`
+
+**Best practices:**
+- Keep configuration files with appropriate permissions (readable only by owner)
+- Do not store sensitive credentials in configuration files
+- Review ping targets to ensure they are legitimate and trusted
+
+### Execution Policy (Windows)
+
+The PowerShell script requires script execution to be enabled:
+
+```powershell
+# Check current execution policy
+Get-ExecutionPolicy
+
+# Set execution policy for current user (recommended)
+Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
+
+# Alternative: Run with bypass for single session
+powershell -ExecutionPolicy Bypass -File .\ConnectivityDropMonitor.ps1
+```
+
+**Security note**: Only use `RemoteSigned` or stricter policies. Avoid `Unrestricted` in production environments.
+
+### macOS/Linux Permissions
+
+**Script Permissions:**
+```bash
+# Set appropriate execute permissions
+chmod 755 macos/ConnectivityDropMonitor.sh
+chmod 644 macos/lib/*.sh # Library files don't need execute bit
+
+# Ensure ownership is correct
+chown -R $USER:$GROUP ~/ConnectivityMonitor
+```
+
+**File Permissions:**
+```bash
+# Secure the base directory
+chmod 700 ~/ConnectivityMonitor
+
+# Logs and reports
+chmod 600 ~/ConnectivityMonitor/logs/*.csv
+chmod 600 ~/ConnectivityMonitor/reports/*.html
+```
+
+### Network Security Considerations
+
+**ICMP Ping Requirements:**
+- Ping commands typically require no special privileges on Windows
+- On Linux/macOS, ping may require setuid or capabilities
+- The monitor uses system `ping` command, not raw sockets
+
+**DNS Security:**
+- DNS health checks query configured DNS servers
+- Ensure DNS targets are trusted (default: google.com)
+- DNS spoofing could affect diagnostics but not monitoring integrity
+
+**External API Calls:**
+The monitor makes HTTPS calls to:
+- `https://ip-api.com/json` — Public IP and ISP detection
+
+These are optional features. If privacy is a concern:
+1. Review the code in `network.py` (Python), `lib/network.sh` (macOS), or PowerShell functions
+2. These calls happen once at startup (not continuously)
+3. No personally identifiable information is sent (simple GET request)
+
+### Data Privacy
+
+**What data is collected:**
+- Ping latency measurements
+- Packet loss statistics
+- Gateway IP addresses
+- Public IP address (via ip-api.com)
+- ISP name (via ip-api.com)
+- WiFi signal strength (if available)
+
+**What is NOT collected:**
+- Personal user information
+- Browsing history
+- Network traffic content
+- Authentication credentials
+
+**Data storage:**
+- All data is stored locally in CSV and HTML files
+- No telemetry is sent to external services
+- Logs can be deleted at any time
+
+### Environment Variables
+
+The monitor supports customization via environment variables:
+
+```bash
+# Customize base directory location
+export CM_BASE_DIR="$HOME/my-custom-location"
+python -m connectivity_monitor
+```
+
+**Security consideration**: Ensure custom directories have appropriate permissions.
+
+## Known Limitations
+
+1. **No Authentication**: Web dashboard has no built-in authentication
+2. **No Encryption**: Local file storage is unencrypted
+3. **No Rate Limiting**: Web API endpoints have no rate limiting
+4. **No Audit Logging**: No security event logging beyond diagnostic logs
+
+## Security Fixes in v4.0
+
+This release includes the following security improvements:
+
+### Fixed Vulnerabilities
+1. **Command Injection (PowerShell)** — Fixed in commit `13b4514`
+ - Old: Used `cmd /c "tracert $target"` (injectable)
+ - New: Uses `System.Diagnostics.ProcessStartInfo` with argument array
+
+2. **HTTP Downgrade Attack** — Fixed in commit `13b4514`
+ - Old: Used `http://ip-api.com` (unencrypted)
+ - New: Uses `https://ip-api.com` (encrypted)
+
+3. **Path Traversal (Python Web Server)** — Fixed in commit `13b4514`
+ - Old: Insufficient validation with `basename()` only
+ - New: Validates resolved path stays within base directory
+
+4. **Code Injection (Bash Config)** — Fixed in commit `13b4514`
+ - Old: Interpolated shell variables into Python code strings
+ - New: Uses heredoc with command-line arguments (safe)
+
+5. **Insecure Temporary Files (macOS)** — Fixed in commit `13b4514`
+ - Old: Predictable filenames `/tmp/cm_traceroute_$$.txt`
+ - New: Uses `mktemp` with random names
+
+### Input Validation Improvements (commit `25b672a`)
+- Poll interval: Must be between 0.1 and 3600 seconds
+- Failure threshold: Must be between 1 and 100
+- Web port: Must be between 1 and 65535
+- Ping targets: Validated as IP addresses or hostnames
+- DNS target: Validated as valid hostname
+
+## Reporting a Security Vulnerability
+
+If you discover a security vulnerability in ConnectivityMonitor:
+
+1. **Do NOT** open a public GitHub issue
+2. **Email** the maintainer: Include the following in your report:
+ - Description of the vulnerability
+ - Steps to reproduce
+ - Potential impact
+ - Suggested fix (if available)
+
+3. **Allow time** for a fix to be developed and released before public disclosure
+
+### Security Contact
+
+For security issues, please open a security advisory on GitHub or create an issue with the `security` label. The maintainer will respond within 7 days.
+
+## Security Checklist for Users
+
+Before deploying ConnectivityMonitor in a production environment:
+
+- [ ] Review all ping targets and ensure they are trusted
+- [ ] Configure firewall rules to restrict web dashboard access
+- [ ] Set appropriate file permissions on logs and config files
+- [ ] Run with least privileges (do not run as root/administrator unless required)
+- [ ] Keep the software updated to receive security fixes
+- [ ] Review logs periodically for unexpected behavior
+- [ ] Disable features you don't need (e.g., DNS checks, public IP detection)
+- [ ] Use a separate monitoring account with limited privileges
+- [ ] Consider network segmentation if running on servers
+
+## Security-Related Configuration
+
+### Disable Public IP Detection
+
+If privacy is a concern, you can disable public IP detection by modifying the code:
+
+**Python**: Comment out the `detect_public_ip()` call in `monitor.py`
+**macOS**: Comment out the `detect_public_ip` call in `ConnectivityDropMonitor.sh`
+**Windows**: Comment out the `DetectPublicIP` call in `ConnectivityDropMonitor.ps1`
+
+### Restrict Web Dashboard Binding
+
+**Python**: Modify `web_server.py` to bind to `127.0.0.1` instead of `0.0.0.0`:
+
+```python
+def start_web_server(port, state, reports_dir, logs_dir, bind="127.0.0.1"):
+```
+
+### Read-Only Logs
+
+To prevent log tampering:
+
+```bash
+# Make logs read-only after rotation
+chmod 444 ~/ConnectivityMonitor/logs/*.csv
+```
+
+## Compliance and Certifications
+
+ConnectivityMonitor is not certified for:
+- Medical use
+- Financial services
+- Critical infrastructure
+- Compliance with specific regulations (HIPAA, PCI-DSS, etc.)
+
+For compliance-critical environments, perform your own security audit and testing.
+
+## License and Disclaimer
+
+This software is provided as-is without warranty. Users are responsible for:
+- Evaluating security for their specific use case
+- Implementing additional security controls as needed
+- Monitoring and maintaining the software
+- Complying with applicable laws and regulations
+
+---
+
+**Last Updated**: 2026-04-06
+**Document Version**: 1.0