docs: BUG-25 security hardening + Phase 9.5 notes

This commit is contained in:
Mathieu Moran
2026-08-15 18:29:35 -04:00
parent 24250e06df
commit 7a9be9d63f
2 changed files with 47 additions and 0 deletions
+26
View File
@@ -58,6 +58,32 @@ When CLOSED, move the entire entry to Resolved — don't duplicate.
- **Verification**: deployed to the Pi; the forced-reconnect path will be exercised on the
next real stall (in-car verification pending).
### BUG-25 — Security hardening for the internet-exposed remote portal
- **Status**: `CLOSED` 2026-08-15
- **Commit**: `24250e0`
- **Findings** (security review of `https://yaris.ceaf.dev`):
1. **Path traversal → arbitrary file write** in `POST /api/v1/dashcam/upload/chunk` — the
`sha256` form field was concatenated into the chunk staging path with no validation, so a
digest like `../../../../tmp/x` escaped `DASHCAM_STORAGE_DIR` and let a device-authenticated
caller write files anywhere inside the container (as root — no `USER` in the Dockerfile).
2. **No login rate limiting** — `/login` was brute-forceable.
3. **Session cookies lacked Secure/SameSite flags.**
- **Fix**:
1. `sha256` is now validated as a 64-char hex digest (`_is_valid_sha256`) on the single-shot
upload, chunk-status, and chunk-upload endpoints before it is used in any filesystem path.
2. In-memory failed-attempt limiter on `/login` — 8 failures / 15 min per client IP (keyed from
`CF-Connecting-IP`, falling back to `X-Forwarded-For` then `remote_addr`) plus per-username;
returns 429 when tripped. The app runs as a single gunicorn worker, so process-local state
is sufficient (documented in a code comment for a future multi-worker deployment).
3. `SESSION_COOKIE_HTTPONLY` + `SESSION_COOKIE_SAMESITE="Lax"` + `SESSION_COOKIE_SECURE`
enabled (Secure can be disabled with `COOKIE_SECURE=0` for plain-HTTP LAN login; env var
documented in `server/.env.example`).
- **Verification**: `python3 -m py_compile server/app.py` clean; deployed to unraid, `/health` OK.
- **Remaining exposure (not code)**: the Pi's own web app (192.168.50.220:5000) is unauthenticated
and binds all interfaces. It is not tunnel-exposed, but a compromised server container could
reach it if the 192.168.10.x (Unraid) and 192.168.50.x (home) subnets route to each other —
isolate those VLANs.
### BUG-22 — Trip max speed ignores GPS-only stretch (shows OBD max only)
- **Status**: `CLOSED` 2026-08-14
- **Commit**: `90cfd7b`