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}` : '';