diff --git a/scripts/render_plugin.py b/scripts/render_plugin.py index 013828ae..818941a3 100644 --- a/scripts/render_plugin.py +++ b/scripts/render_plugin.py @@ -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)') diff --git a/src/base_classes/sports.py b/src/base_classes/sports.py index acee7565..7e6251e7 100644 --- a/src/base_classes/sports.py +++ b/src/base_classes/sports.py @@ -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) diff --git a/src/common/permission_utils.py b/src/common/permission_utils.py index f2b09b14..0ce7bbc9 100644 --- a/src/common/permission_utils.py +++ b/src/common/permission_utils.py @@ -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', diff --git a/src/display_controller.py b/src/display_controller.py index c58cb27f..00de2eb2 100644 --- a/src/display_controller.py +++ b/src/display_controller.py @@ -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. diff --git a/src/display_manager.py b/src/display_manager.py index 174b66b3..43433582 100644 --- a/src/display_manager.py +++ b/src/display_manager.py @@ -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" diff --git a/src/plugin_system/state_reconciliation.py b/src/plugin_system/state_reconciliation.py index c512cfe6..d94f42ca 100644 --- a/src/plugin_system/state_reconciliation.py +++ b/src/plugin_system/state_reconciliation.py @@ -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}") diff --git a/src/wifi_manager.py b/src/wifi_manager.py index 86cd6ef1..21b87035 100644 --- a/src/wifi_manager.py +++ b/src/wifi_manager.py @@ -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 ) diff --git a/web_interface/app.py b/web_interface/app.py index 4e653de4..4b02232d 100644 --- a/web_interface/app.py +++ b/web_interface/app.py @@ -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: diff --git a/web_interface/blueprints/pages_v3.py b/web_interface/blueprints/pages_v3.py index 89e550de..00fe6b1d 100644 --- a/web_interface/blueprints/pages_v3.py +++ b/web_interface/blueprints/pages_v3.py @@ -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 diff --git a/web_interface/start.py b/web_interface/start.py index 885bd136..9af289a6 100644 --- a/web_interface/start.py +++ b/web_interface/start.py @@ -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