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:
@@ -734,6 +734,27 @@ BANGUI_ALLOWED_LOG_DIRS="/var/log,/config/log" # Default
|
||||
BANGUI_ALLOWED_LOG_DIRS="/var/log,/config/log,/home/app/logs" # Custom directory
|
||||
```
|
||||
|
||||
### Log Target Validation (fail2ban)
|
||||
|
||||
The `log_target` field on the global config endpoint (`PUT /api/config/global`) is critical for security because fail2ban runs as root. Users can only set log targets to:
|
||||
|
||||
1. **Special values:** `STDOUT`, `STDERR`, `SYSLOG` (case-insensitive)
|
||||
2. **File paths:** Must resolve to one of the configured allowed directories (same allowlist as log paths)
|
||||
|
||||
**Why This Matters:**
|
||||
- fail2ban creates/opens files with root privileges. Without validation, an attacker could write to arbitrary system paths (e.g., `/etc/cron.d/malicious_script`).
|
||||
- Validation occurs at **both** the Pydantic model layer (`GlobalConfigUpdate.validate_log_target()`) **and** the service layer (`update_global_config()`) for defense in depth.
|
||||
- This prevents both HTTP and non-HTTP attack vectors.
|
||||
|
||||
**Implementation:**
|
||||
```python
|
||||
# Model layer: Automatic validation via @field_validator
|
||||
update = GlobalConfigUpdate(log_target="/etc/passwd") # Raises ValidationError → HTTP 422
|
||||
|
||||
# Service layer: Defense in depth
|
||||
await config_service.update_global_config(socket_path, update) # Validates again before sending to fail2ban
|
||||
```
|
||||
|
||||
### Login Rate Limiting
|
||||
|
||||
The login endpoint (`POST /api/auth/login`) is protected against brute-force attacks using an in-memory rate limiter.
|
||||
|
||||
@@ -1,32 +1,3 @@
|
||||
## TASK-019 — `session_secret` has no minimum-length enforcement
|
||||
|
||||
**Severity:** Medium
|
||||
|
||||
### Where found
|
||||
`backend/app/config.py` — `session_secret: str = Field(..., description="...")`. No `min_length` constraint.
|
||||
|
||||
### Why this is needed
|
||||
`session_secret` is the HMAC key used to sign all session tokens. A secret shorter than 32 characters (256 bits) significantly weakens HMAC-SHA256. The app currently accepts any non-empty string, including a single character.
|
||||
|
||||
### Goal
|
||||
Enforce a minimum secret length of 32 characters at startup.
|
||||
|
||||
### What to do
|
||||
1. Add `min_length=32` to the `session_secret` Field definition.
|
||||
2. Update the error message to explain: `"session_secret must be at least 32 characters. Generate one with: python -c \"import secrets; print(secrets.token_hex(32))\""`.
|
||||
|
||||
### Possible traps and issues
|
||||
- This is a breaking change for any existing deployment where the secret is shorter than 32 characters. Include a migration note in the changelog.
|
||||
- The debug compose file uses `dev-secret-do-not-use-in-production` (42 chars) — this already passes the 32-char check, so dev environments are unaffected.
|
||||
|
||||
### Docs changes needed
|
||||
- `Backend-Development.md` — document the `session_secret` constraint.
|
||||
|
||||
### Doc references
|
||||
- [Backend-Development.md](Backend-Development.md) — configuration reference
|
||||
|
||||
---
|
||||
|
||||
## TASK-020 — `log_target` accepts arbitrary paths — root file write via fail2ban (CRITICAL)
|
||||
|
||||
**Severity:** Critical
|
||||
|
||||
Reference in New Issue
Block a user