TASK-010: Replace .split() with shlex.split() for fail2ban_start_command
- Add @field_validator for fail2ban_start_command to validate with shlex.split() at startup, catching misconfigured commands with mismatched quotes - Replace .split() with shlex.split() in jail_config.py line 450 - Replace .split() with shlex.split() in config_misc.py line 154 - Update Backend-Development.md with configuration documentation explaining quoted path handling and common pitfalls - Add comprehensive test suite (8 tests) covering valid commands, quoted paths, and mismatched quote errors This fix ensures commands like '/opt/my tools/fail2ban-client' start are correctly parsed as two tokens instead of three, preventing execution failures when the path contains spaces. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1,39 +1,3 @@
|
||||
## TASK-009 — Blocklist URL has no scheme/host validation — SSRF risk
|
||||
|
||||
**Severity:** High
|
||||
|
||||
### Where found
|
||||
`backend/app/models/blocklist.py` — `BlocklistSourceCreate.url: str = Field(..., min_length=1)`. `backend/app/services/blocklist_service.py` — `preview_source()` and `_download_text_with_retries()`.
|
||||
|
||||
### Why this is needed
|
||||
An authenticated user can supply `file:///etc/passwd`, `http://169.254.169.254/latest/meta-data/` (AWS metadata service), `http://10.0.0.1/admin`, or `http://localhost:8000/api/setup` as a blocklist URL. The backend fetches it and either returns its contents in the preview response or attempts to parse it as an IP list. This is a Server-Side Request Forgery (SSRF) vulnerability.
|
||||
|
||||
### Goal
|
||||
Restrict blocklist URLs to safe, public HTTP/HTTPS endpoints only.
|
||||
|
||||
### What to do
|
||||
1. Change `url: str` to `url: AnyHttpUrl` in `BlocklistSourceCreate` — this rejects `file://`, `ftp://`, and other non-http schemes.
|
||||
2. Add a `@field_validator("url")` that:
|
||||
- Parses the hostname.
|
||||
- Resolves it via `socket.getaddrinfo()` (or uses `ipaddress.ip_address()` if it is a bare IP).
|
||||
- Rejects RFC 1918 private ranges (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`), link-local (`169.254.0.0/16`), and IPv6 equivalents.
|
||||
3. Add a validator for `http://` URLs — consider requiring `https://` only, or adding a configuration flag.
|
||||
|
||||
### Possible traps and issues
|
||||
- DNS rebinding: the hostname resolves to a public IP at validation time but to a private IP at fetch time. Mitigate by re-validating the final connection IP in aiohttp (custom `TCPConnector` or response callback).
|
||||
- `AnyHttpUrl` allows ports — ensure `http://evil.com:22/` is also blocked or at least safe.
|
||||
- `socket.getaddrinfo()` is blocking — use `asyncio.get_event_loop().getaddrinfo()` or run in executor.
|
||||
|
||||
### Docs changes needed
|
||||
- `Features.md` — document URL validation constraints for blocklist sources.
|
||||
- `Backend-Development.md` — SSRF prevention pattern.
|
||||
|
||||
### Doc references
|
||||
- [Features.md](Features.md) — blocklist management
|
||||
- [Backend-Development.md](Backend-Development.md) — input validation
|
||||
|
||||
---
|
||||
|
||||
## TASK-010 — `fail2ban_start_command` split with `.split()` instead of `shlex.split()`
|
||||
|
||||
**Severity:** Low
|
||||
|
||||
Reference in New Issue
Block a user