2 Commits

Author SHA1 Message Date
Chuck
65d2ff3751 fix(security): comprehensive error handling cleanup
- Remove all traceback.format_exc() from client responses (33 remaining instances)
- Sanitize str(e) from client-facing messages, replacing with generic error messages
- Replace ~65 bare print() calls with structured logger.exception/error/warning/info/debug
- Remove ~35 redundant inline `import traceback` and `import logging` statements
- Convert logging.error/warning calls to use module-level named logger
- Fix WiFi endpoints that created redundant inline logger instances
- Add logger.exception() at all WebInterfaceError.from_exception() call sites
- Fix from_exception() in errors.py to use safe messages instead of raw str(exception)
- Apply consistent [Tag] prefixes to all logger calls for production triage

Only safe, user-input-derived str(e) kept: json.JSONDecodeError handlers (400 responses).
Subprocess template print(stdout) calls preserved (not error logging).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 15:04:57 -04:00
Chuck
a0567a66cc fix: catch ConfigError in display preview generator
PR #282 narrowed bare except blocks but missed ConfigError from
config_manager.load_config(), which wraps FileNotFoundError,
JSONDecodeError, and OSError. Without this, a corrupt or missing
config crashes the display preview SSE endpoint instead of falling
back to 128x64 defaults.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 15:01:45 -04:00
3 changed files with 229 additions and 285 deletions

View File

@@ -214,12 +214,40 @@ class WebInterfaceError:
return cls( return cls(
error_code=error_code, error_code=error_code,
message=str(exception), message=cls._safe_message(error_code),
details=cls._get_exception_details(exception), details=cls._get_exception_details(exception),
context=error_context, context=error_context,
original_error=exception original_error=exception
) )
@classmethod
def _safe_message(cls, error_code: ErrorCode) -> str:
"""Get a safe, user-facing message for an error code."""
messages = {
ErrorCode.CONFIG_SAVE_FAILED: "Failed to save configuration",
ErrorCode.CONFIG_LOAD_FAILED: "Failed to load configuration",
ErrorCode.CONFIG_VALIDATION_FAILED: "Configuration validation failed",
ErrorCode.CONFIG_ROLLBACK_FAILED: "Failed to rollback configuration",
ErrorCode.PLUGIN_NOT_FOUND: "Plugin not found",
ErrorCode.PLUGIN_INSTALL_FAILED: "Failed to install plugin",
ErrorCode.PLUGIN_UPDATE_FAILED: "Failed to update plugin",
ErrorCode.PLUGIN_UNINSTALL_FAILED: "Failed to uninstall plugin",
ErrorCode.PLUGIN_LOAD_FAILED: "Failed to load plugin",
ErrorCode.PLUGIN_OPERATION_CONFLICT: "A plugin operation is already in progress",
ErrorCode.VALIDATION_ERROR: "Validation error",
ErrorCode.SCHEMA_VALIDATION_FAILED: "Schema validation failed",
ErrorCode.INVALID_INPUT: "Invalid input",
ErrorCode.NETWORK_ERROR: "Network error",
ErrorCode.API_ERROR: "API error",
ErrorCode.TIMEOUT: "Operation timed out",
ErrorCode.PERMISSION_DENIED: "Permission denied",
ErrorCode.FILE_PERMISSION_ERROR: "File permission error",
ErrorCode.SYSTEM_ERROR: "A system error occurred",
ErrorCode.SERVICE_UNAVAILABLE: "Service unavailable",
ErrorCode.UNKNOWN_ERROR: "An unexpected error occurred",
}
return messages.get(error_code, "An unexpected error occurred")
@classmethod @classmethod
def _infer_error_code(cls, exception: Exception) -> ErrorCode: def _infer_error_code(cls, exception: Exception) -> ErrorCode:
"""Infer error code from exception type.""" """Infer error code from exception type."""

View File

@@ -11,6 +11,7 @@ from datetime import datetime, timedelta
sys.path.insert(0, str(Path(__file__).parent.parent)) sys.path.insert(0, str(Path(__file__).parent.parent))
from src.config_manager import ConfigManager from src.config_manager import ConfigManager
from src.exceptions import ConfigError
from src.plugin_system.plugin_manager import PluginManager from src.plugin_system.plugin_manager import PluginManager
from src.plugin_system.store_manager import PluginStoreManager from src.plugin_system.store_manager import PluginStoreManager
from src.plugin_system.saved_repositories import SavedRepositoriesManager from src.plugin_system.saved_repositories import SavedRepositoriesManager
@@ -492,7 +493,7 @@ def display_preview_generator():
parallel = main_config.get('display', {}).get('hardware', {}).get('parallel', 1) parallel = main_config.get('display', {}).get('hardware', {}).get('parallel', 1)
width = cols * chain_length width = cols * chain_length
height = rows * parallel height = rows * parallel
except (KeyError, TypeError, ValueError): except (KeyError, TypeError, ValueError, ConfigError):
width = 128 width = 128
height = 64 height = 64

File diff suppressed because it is too large Load Diff