mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-05-16 18:23:32 +00:00
fix: address Bandit B108/B110 across production code
B110 (try/except/pass): - display_controller.py: narrow 'except Exception' to 'except AttributeError' for get_offset_frame() — plugins not having this optional method is the expected case, not all exceptions - config_manager.py: B110 already resolved by the earlier removal of the dead secrets-loading block (the except/pass was inside it) - All other except/pass blocks in src/ and web_interface/ are intentional (last-resort recovery, best-effort fallbacks, non-critical startup probes). Annotated each with # nosec B110 and a brief inline reason so the decision is explicit for future reviewers. - Test files and plugin-repos B110 suppressed via Codacy API (not prod code). B108 (/tmp usage): - permission_utils.py: /tmp listed to PREVENT permission changes on it — not used as a temp path. Annotated # nosec B108. - display_manager.py: fixed snapshot path is intentional (web UI reads same path); path-check guard also annotated. - wifi_manager.py: named /tmp files match the sudoers allowlist installed with the system (the paths are hard-coded in both places by design). Annotated all six open/cp references # nosec B108. - scripts/render_plugin.py: dev script default overridable by user. Annotated. - web_interface/app.py: reads the same fixed path written by display_manager. Annotated # nosec B108. - Test files suppressed via Codacy API. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -81,7 +81,7 @@ def main() -> int:
|
||||
help='Plugin config as JSON string')
|
||||
parser.add_argument('--mock-data', '-m', default=None,
|
||||
help='Path to JSON file with mock cache data')
|
||||
parser.add_argument('--output', '-o', default='/tmp/plugin_render.png',
|
||||
parser.add_argument('--output', '-o', default='/tmp/plugin_render.png', # nosec B108 - dev script default; user can override
|
||||
help='Output PNG path (default: /tmp/plugin_render.png)')
|
||||
parser.add_argument('--width', type=int, default=128, help='Display width (default: 128)')
|
||||
parser.add_argument('--height', type=int, default=32, help='Display height (default: 32)')
|
||||
|
||||
@@ -172,7 +172,7 @@ class SportsCore(ABC):
|
||||
|
||||
try:
|
||||
fallbacks.append(Path.home() / ".ledmatrix" / "logos" / self.sport_key)
|
||||
except Exception:
|
||||
except Exception: # nosec B110 - Path.home() raises RuntimeError for service users; fallback list continues
|
||||
pass
|
||||
|
||||
fallbacks.append(Path(tempfile.gettempdir()) / "ledmatrix_logos" / self.sport_key)
|
||||
|
||||
@@ -17,7 +17,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
# System directories that should never have their permissions modified
|
||||
# These directories have special system-level permissions that must be preserved
|
||||
PROTECTED_SYSTEM_DIRECTORIES = {
|
||||
PROTECTED_SYSTEM_DIRECTORIES = { # nosec B108 - these are checked to PREVENT permission changes, not to use as temp paths
|
||||
'/tmp',
|
||||
'/var/tmp',
|
||||
'/dev',
|
||||
|
||||
@@ -813,8 +813,8 @@ class DisplayController:
|
||||
# 1. Explicit hook — plugin opted in with get_offset_frame()
|
||||
try:
|
||||
follower_frame = plugin_instance.get_offset_frame(offset)
|
||||
except Exception:
|
||||
pass
|
||||
except AttributeError:
|
||||
pass # Most plugins don't implement get_offset_frame; that's expected
|
||||
|
||||
# 2. Auto-detect — plugin has a scroll_helper (standard pattern for all
|
||||
# scroll plugins). Works with zero plugin code changes.
|
||||
|
||||
@@ -32,7 +32,7 @@ class DisplayManager:
|
||||
# When True, update_display() and clear() skip hardware writes (used during off-screen content capture)
|
||||
self._capture_mode_active = False
|
||||
# Snapshot settings for web preview integration (service writes, web reads)
|
||||
self._snapshot_path = "/tmp/led_matrix_preview.png"
|
||||
self._snapshot_path = "/tmp/led_matrix_preview.png" # nosec B108 - fixed path intentional; web UI reads same path
|
||||
self._snapshot_min_interval_sec = 0.2 # max ~5 fps
|
||||
self._last_snapshot_ts = 0.0
|
||||
|
||||
@@ -150,7 +150,7 @@ class DisplayManager:
|
||||
self.draw.rectangle([0, 0, fallback_width - 1, fallback_height - 1], outline=(255, 0, 0))
|
||||
self.draw.line([0, 0, fallback_width - 1, fallback_height - 1], fill=(0, 255, 0))
|
||||
self.draw.text((2, max(0, (fallback_height // 2) - 4)), "Simulation", fill=(0, 128, 255))
|
||||
except Exception:
|
||||
except Exception: # nosec B110 - best-effort fallback visualization; drawing errors must not crash startup
|
||||
# Best-effort; ignore drawing errors in fallback
|
||||
pass
|
||||
logger.error(f"Matrix initialization failed, using fallback mode with size {fallback_width}x{fallback_height}. Error: {e}")
|
||||
@@ -894,7 +894,7 @@ class DisplayManager:
|
||||
# Never modify /tmp permissions - it has special system permissions (1777)
|
||||
# that must not be changed or it breaks apt and other system tools
|
||||
parent_dir = snapshot_path_obj.parent
|
||||
if parent_dir and str(parent_dir) != '/tmp':
|
||||
if parent_dir and str(parent_dir) != '/tmp': # nosec B108 - guard to skip /tmp for permission ops
|
||||
ensure_directory_permissions(parent_dir, get_assets_dir_mode())
|
||||
# Write atomically: temp then replace
|
||||
tmp_path = f"{self._snapshot_path}.tmp"
|
||||
|
||||
@@ -234,7 +234,7 @@ class StateReconciliation:
|
||||
'version': manifest.get('version'),
|
||||
'name': manifest.get('name')
|
||||
}
|
||||
except Exception:
|
||||
except Exception: # nosec B110 - corrupt/unreadable manifest; skip this plugin, outer except logs
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Error reading disk state: {e}")
|
||||
|
||||
@@ -689,7 +689,7 @@ class WiFiManager:
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_IP_FORWARD_SAVE_PATH = Path("/tmp/ledmatrix_ip_forward_saved")
|
||||
_IP_FORWARD_SAVE_PATH = Path("/tmp/ledmatrix_ip_forward_saved") # nosec B108 - process-specific named file; device is single-user RPi
|
||||
|
||||
def _validate_ap_config(self) -> Tuple[str, int]:
|
||||
"""Return a sanitized (ssid, channel) pair from config, falling back to defaults."""
|
||||
@@ -890,14 +890,14 @@ class WiFiManager:
|
||||
"""
|
||||
try:
|
||||
content = f"# LEDMatrix captive portal: resolve all hostnames to AP\naddress=/#/{ap_ip}\n"
|
||||
with open("/tmp/ledmatrix-nm-dnsmasq.conf", "w") as f:
|
||||
with open("/tmp/ledmatrix-nm-dnsmasq.conf", "w") as f: # nosec B108 - named file matches sudoers allowlist; single-user device
|
||||
f.write(content)
|
||||
subprocess.run(
|
||||
["sudo", "mkdir", "-p", str(NM_DNSMASQ_SHARED_DIR)],
|
||||
capture_output=True, timeout=5
|
||||
)
|
||||
subprocess.run(
|
||||
["sudo", "cp", "/tmp/ledmatrix-nm-dnsmasq.conf", str(NM_DNSMASQ_SHARED_CONF)],
|
||||
["sudo", "cp", "/tmp/ledmatrix-nm-dnsmasq.conf", str(NM_DNSMASQ_SHARED_CONF)], # nosec B108
|
||||
capture_output=True, timeout=5
|
||||
)
|
||||
logger.info(f"Wrote NM dnsmasq captive-portal config: {NM_DNSMASQ_SHARED_CONF}")
|
||||
@@ -1401,7 +1401,7 @@ class WiFiManager:
|
||||
# Last resort: enable AP mode
|
||||
try:
|
||||
self.enable_ap_mode()
|
||||
except Exception:
|
||||
except Exception: # nosec B110 - last-resort recovery; if AP enable fails there's nothing left to try
|
||||
pass
|
||||
return False, str(e)
|
||||
|
||||
@@ -2324,12 +2324,12 @@ ignore_broadcast_ssid=0
|
||||
"""
|
||||
|
||||
# Write config (requires sudo)
|
||||
with open("/tmp/hostapd.conf", 'w') as f:
|
||||
with open("/tmp/hostapd.conf", 'w') as f: # nosec B108 - named file matches sudoers allowlist; single-user device
|
||||
f.write(config_content)
|
||||
|
||||
# Copy to final location with sudo
|
||||
subprocess.run(
|
||||
["sudo", "cp", "/tmp/hostapd.conf", str(HOSTAPD_CONFIG_PATH)],
|
||||
["sudo", "cp", "/tmp/hostapd.conf", str(HOSTAPD_CONFIG_PATH)], # nosec B108
|
||||
timeout=10
|
||||
)
|
||||
|
||||
@@ -2394,12 +2394,12 @@ address=/detectportal.firefox.com/192.168.4.1
|
||||
"""
|
||||
|
||||
# Write config (requires sudo)
|
||||
with open("/tmp/dnsmasq.conf", 'w') as f:
|
||||
with open("/tmp/dnsmasq.conf", 'w') as f: # nosec B108 - named file matches sudoers allowlist; single-user device
|
||||
f.write(config_content)
|
||||
|
||||
# Copy to final location with sudo
|
||||
subprocess.run(
|
||||
["sudo", "cp", "/tmp/dnsmasq.conf", str(DNSMASQ_CONFIG_PATH)],
|
||||
["sudo", "cp", "/tmp/dnsmasq.conf", str(DNSMASQ_CONFIG_PATH)], # nosec B108
|
||||
timeout=10
|
||||
)
|
||||
|
||||
|
||||
@@ -324,7 +324,7 @@ def after_request_logging(response):
|
||||
duration_ms=duration_ms,
|
||||
ip_address=ip_address
|
||||
)
|
||||
except Exception:
|
||||
except Exception: # nosec B110 - request logging must never interrupt a live HTTP response
|
||||
pass # Don't break response if logging fails
|
||||
return response
|
||||
|
||||
@@ -502,7 +502,7 @@ def display_preview_generator():
|
||||
from PIL import Image
|
||||
import io
|
||||
|
||||
snapshot_path = "/tmp/led_matrix_preview.png"
|
||||
snapshot_path = "/tmp/led_matrix_preview.png" # nosec B108 - fixed path matches display_manager; only read here
|
||||
last_modified = None
|
||||
|
||||
# Get display dimensions from config
|
||||
@@ -542,7 +542,7 @@ def display_preview_generator():
|
||||
}
|
||||
last_modified = current_modified
|
||||
yield preview_data
|
||||
except Exception:
|
||||
except Exception: # nosec B110 - SSE preview file may be mid-write; transient error, skip this update
|
||||
# File might be being written, skip this update
|
||||
pass
|
||||
else:
|
||||
|
||||
@@ -395,7 +395,7 @@ def _load_plugin_config_partial(plugin_id):
|
||||
config['images'] = config.get('images', []) + new_images
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not load metadata for {plugin_id}: {e}")
|
||||
except Exception:
|
||||
except Exception: # nosec B110 - metadata pre-load is optional; schema loads fully below
|
||||
pass # Will load schema properly below
|
||||
|
||||
# Get plugin schema
|
||||
|
||||
@@ -25,7 +25,7 @@ def get_local_ips():
|
||||
)
|
||||
if result.returncode == 0 and result.stdout.strip() == "active":
|
||||
ips.append("192.168.4.1 (AP Mode)")
|
||||
except Exception:
|
||||
except Exception: # nosec B110 - AP mode IP detection is non-critical startup info; systemctl may not exist
|
||||
pass
|
||||
|
||||
# Get IPs from hostname -I
|
||||
|
||||
Reference in New Issue
Block a user