From cd0b725f3e0eeadf74cc9bf773b24e6635ba0d8b Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Mon, 11 May 2026 09:31:25 -0400 Subject: [PATCH] fix: LLDP port label bug, suppression SQL dead code, avatar path hardening MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - inspector.html: fix LLDP neighbor label in port blocks — port.lldp_table never exists; data is at port.lldp (dict with system_name/chassis_id); both port block renderers corrected - db.py: remove dead 'target_detail IS NULL' branch in suppression check — target_detail is always stored as '' not NULL; query simplified to target_detail='' - app.py: resolve cache_dir/cache_file/sentinel to absolute paths; guard against path escape before use - app.py: wrap sentinel os.path.getmtime() in try/except OSError to handle TOCTOU deletion race Co-Authored-By: Claude Sonnet 4.6 --- app.py | 19 ++++++++++++++----- db.py | 2 +- templates/inspector.html | 12 ++++-------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/app.py b/app.py index 2868e70..6997b32 100644 --- a/app.py +++ b/app.py @@ -539,10 +539,16 @@ def api_avatar(): # Build a safe cache filename from the username (alphanumeric + - _ .) safe_name = re.sub(r'[^a-zA-Z0-9._-]', '_', username) - cache_dir = ldap_cfg.get('cache_dir', os.path.join(tempfile.gettempdir(), 'gandalf_avatars')) + cache_dir = os.path.abspath( + ldap_cfg.get('cache_dir', os.path.join(tempfile.gettempdir(), 'gandalf_avatars')) + ) os.makedirs(cache_dir, exist_ok=True) - cache_file = os.path.join(cache_dir, f'user_{safe_name}.jpg') - sentinel = os.path.join(cache_dir, f'user_{safe_name}.none') + cache_file = os.path.abspath(os.path.join(cache_dir, f'user_{safe_name}.jpg')) + sentinel = os.path.abspath(os.path.join(cache_dir, f'user_{safe_name}.none')) + # Guard against path escape (shouldn't happen with sanitised safe_name, but be explicit) + if not cache_file.startswith(cache_dir + os.sep) or not sentinel.startswith(cache_dir + os.sep): + logger.error(f'Avatar path escape detected for user {username!r}') + return '', 404 try: cache_ttl = int(ldap_cfg.get('cache_ttl', 3600)) except (ValueError, TypeError): @@ -557,8 +563,11 @@ def api_avatar(): max_age=cache_ttl, conditional=True) # Skip LDAP if we already know this user has no avatar - if os.path.exists(sentinel) and now - os.path.getmtime(sentinel) < cache_ttl: - return '', 404 + try: + if os.path.exists(sentinel) and now - os.path.getmtime(sentinel) < cache_ttl: + return '', 404 + except OSError: + pass # Query lldap bind_pw = ldap_cfg.get('bind_pw', '') diff --git a/db.py b/db.py index 6b20a63..9791c67 100644 --- a/db.py +++ b/db.py @@ -365,7 +365,7 @@ def is_suppressed(target_type: str, target_name: str, target_detail: str = '') - """SELECT id FROM suppression_rules WHERE active=TRUE AND (expires_at IS NULL OR expires_at > NOW()) AND target_type=%s AND target_name=%s - AND (target_detail IS NULL OR target_detail='') LIMIT 1""", + AND target_detail='' LIMIT 1""", (target_type, target_name), ) if cur.fetchone(): diff --git a/templates/inspector.html b/templates/inspector.html index 98d0f47..cd0dbc9 100644 --- a/templates/inspector.html +++ b/templates/inspector.html @@ -107,10 +107,8 @@ function portBlockHtml(idx, port, swName, sfpBlock) { const sfpCls = sfpBlock ? ' sfp-block' : ''; const speedTxt = portSpeedLabel(port); // LLDP neighbor: first 6 chars of hostname - const lldpName = (port && port.lldp_table && port.lldp_table.length) - ? escHtml((port.lldp_table[0].chassis_id_subtype === 'local' - ? port.lldp_table[0].chassis_id - : port.lldp_table[0].system_name || port.lldp_table[0].chassis_id || '').slice(0, 6)) + const lldpName = (port && port.lldp && (port.lldp.system_name || port.lldp.chassis_id)) + ? escHtml((port.lldp.system_name || port.lldp.chassis_id || '').slice(0, 6)) : ''; const lldpHtml = lldpName ? `${lldpName}` : ''; const speedHtml = speedTxt ? `${speedTxt}` : ''; @@ -162,10 +160,8 @@ function renderChassis(swName, sw) { const state = portBlockState(port); const title = port ? escHtml(port.name) : `Port ${idx}`; const speedTxt = portSpeedLabel(port); - const lldpName = (port && port.lldp_table && port.lldp_table.length) - ? escHtml((port.lldp_table[0].chassis_id_subtype === 'local' - ? port.lldp_table[0].chassis_id - : port.lldp_table[0].system_name || port.lldp_table[0].chassis_id || '').slice(0, 6)) + const lldpName = (port && port.lldp && (port.lldp.system_name || port.lldp.chassis_id)) + ? escHtml((port.lldp.system_name || port.lldp.chassis_id || '').slice(0, 6)) : ''; const speedHtml = speedTxt ? `${speedTxt}` : ''; const lldpHtml = lldpName ? `${lldpName}` : '';