From 61408645a5f828e4bf617f3b7c3feaefa0ac76e8 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Mon, 11 May 2026 08:45:28 -0400 Subject: [PATCH] fix: LLDP input validation, mgmt_ip early validation, poll timer cleanup, monitor backoff - app.py: validate server_name from LLDP with fullmatch before use in logs/lookups (prevents log injection) - app.py: validate each mgmt_ip candidate before assigning host_ip (avoids assigning non-IP string that then fails later check) - app.py: log actual exception in link_stats JSON parse error - inspector.html: clear _diagPollTimer in closePanel() so timer doesn't orphan when panel is closed mid-poll - monitor.py: sleep 30s after a monitor loop exception before resuming normal poll interval Co-Authored-By: Claude Sonnet 4.6 --- app.py | 18 +++++++++++++----- monitor.py | 1 + templates/inspector.html | 1 + 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/app.py b/app.py index 739e7c1..a71cbe8 100644 --- a/app.py +++ b/app.py @@ -371,8 +371,8 @@ def api_diagnose_start(): return jsonify({'error': 'No link_stats data available'}), 503 try: link_data = json.loads(raw) - except Exception: - logger.error('Failed to parse link_stats JSON in /api/diagnose') + except Exception as e: + logger.error(f'Failed to parse link_stats JSON in /api/diagnose: {e}') return jsonify({'error': 'Internal data error'}), 500 switches = link_data.get('unifi_switches', {}) @@ -396,6 +396,9 @@ def api_diagnose_start(): return jsonify({'error': 'No LLDP neighbor data for this port'}), 400 server_name = lldp['system_name'] + if not re.fullmatch(r'[a-zA-Z0-9._-]+', server_name): + logger.error(f'Refusing diagnostic: invalid server_name from LLDP: {server_name!r}') + return jsonify({'error': 'LLDP neighbor name contains invalid characters'}), 400 lldp_port_id = lldp.get('port_id', '') # Find matching host + interface in link_stats hosts @@ -421,9 +424,14 @@ def api_diagnose_start(): # Resolve host IP from link_stats host data host_ip = (server_ifaces.get(matched_iface) or {}).get('host_ip') if not host_ip: - # Fallback: use LLDP mgmt IPs - mgmt_ips = lldp.get('mgmt_ips') or [] - host_ip = mgmt_ips[0] if mgmt_ips else None + # Fallback: use first valid IP from LLDP mgmt IPs + for candidate in (lldp.get('mgmt_ips') or []): + try: + ipaddress.ip_address(candidate) + host_ip = candidate + break + except ValueError: + continue if not host_ip: return jsonify({'error': 'Cannot determine host IP for SSH'}), 400 diff --git a/monitor.py b/monitor.py index 63b16a8..445912d 100644 --- a/monitor.py +++ b/monitor.py @@ -966,6 +966,7 @@ class NetworkMonitor: except Exception as e: logger.error(f'Monitor loop error: {e}', exc_info=True) + time.sleep(30) time.sleep(self.poll_interval) diff --git a/templates/inspector.html b/templates/inspector.html index d33e9a6..98d0f47 100644 --- a/templates/inspector.html +++ b/templates/inspector.html @@ -231,6 +231,7 @@ function selectPort(el) { } function closePanel() { + if (_diagPollTimer) { clearInterval(_diagPollTimer); _diagPollTimer = null; } document.getElementById('inspector-panel').classList.remove('open'); document.querySelectorAll('.switch-port-block.selected') .forEach(el => el.classList.remove('selected'));