TASK-020: Fix log_target security vulnerability (defense in depth)
**Issue:** - log_target accepted arbitrary paths, allowing authenticated users to write files as root via fail2ban (e.g., /etc/cron.d/bangui-pwned) - fail2ban runs as root and opens files specified in log_target **Solution:** 1. **Model layer validation:** Already existed in GlobalConfigUpdate, prevents invalid paths before reaching service 2. **Service layer validation:** Added defensive check in update_global_config() that validates log_target even if model validation is bypassed 3. **New validation helper:** Added validate_log_target() utility that accepts special values (STDOUT, STDERR, SYSLOG) or paths within allowed directories **Changes:** - app/utils/path_utils.py: Added validate_log_target() helper - app/services/config_service.py: Added service-layer validation before sending command to fail2ban - backend/tests: Fixed session_secret length issues in fixtures (min 32 chars) - backend/tests: Added tests for valid special log targets - Docs/Backend-Development.md: Documented log_target security requirements **Test Coverage:** - Model validation rejects /etc/passwd (existing test) - Model validation accepts STDOUT, STDERR, SYSLOG special values - Model validation accepts paths in allowed directories - Service layer validation tested with special values Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -59,6 +59,7 @@ from app.utils.fail2ban_response import (
|
||||
ok,
|
||||
to_dict,
|
||||
)
|
||||
from app.utils.path_utils import validate_log_target
|
||||
|
||||
log: structlog.stdlib.BoundLogger = structlog.get_logger()
|
||||
|
||||
@@ -421,8 +422,15 @@ async def update_global_config(socket_path: str, update: GlobalConfigUpdate) ->
|
||||
|
||||
Raises:
|
||||
ConfigOperationError: If a ``set`` command is rejected.
|
||||
ConfigValidationError: If log_target validation fails.
|
||||
~app.utils.fail2ban_client.Fail2BanConnectionError: Socket unreachable.
|
||||
"""
|
||||
if update.log_target is not None:
|
||||
try:
|
||||
validate_log_target(update.log_target)
|
||||
except ValueError as e:
|
||||
raise ConfigValidationError(str(e)) from e
|
||||
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
|
||||
async def _set_global(key: str, value: Fail2BanToken) -> None:
|
||||
|
||||
Reference in New Issue
Block a user