diff --git a/BUGS.md b/BUGS.md index d0102e5..3194f93 100644 --- a/BUGS.md +++ b/BUGS.md @@ -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` diff --git a/PLAN.md b/PLAN.md index a451dc2..8692b3b 100644 --- a/PLAN.md +++ b/PLAN.md @@ -411,6 +411,27 @@ confirmed matching row counts on the server via direct psql query, live upload s Exposed externally at `https://yaris.ceaf.dev` via the user's Cloudflare Tunnel (public hostname added in the Cloudflare dashboard, pointing at `192.168.10.10:8098`). +### Phase 9.5: Remote-portal security hardening — ✅ DONE (2026-08-15) +Security review of the internet-exposed `https://yaris.ceaf.dev` portal (BUG-25) surfaced three +issues, all fixed in `24250e0`: + +1. **Path traversal → arbitrary file write** in `POST /api/v1/dashcam/upload/chunk`: the `sha256` + form field was joined into the chunk staging path with no validation, so a crafted digest + (`../../...`) could escape `DASHCAM_STORAGE_DIR` and write files anywhere inside the container + as root (device-auth required, but the device key lives on the Pi). `sha256` is now validated + as a strict 64-char hex digest on the single-shot, chunk-status, and chunk-upload endpoints. +2. **No login rate limiting** — added an in-memory failed-attempt limiter (8 failures / 15 min + per `CF-Connecting-IP` + per-username, returns 429). Single gunicorn worker makes process-local + state sufficient. +3. **Session cookies** — now `HttpOnly` + `SameSite=Lax` + `Secure` (Secure can be disabled via + `COOKIE_SECURE=0` for plain-HTTP LAN login; documented in `server/.env.example`). + +**Remaining exposure (not code):** the Pi's own web app (192.168.50.220:5000) is unauthenticated +and binds all interfaces. It is *not* reachable through the Cloudflare Tunnel, 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 and/or enable Cloudflare Bot Fight Mode + the free 1 +rate-limiting rule on `/login`. + ### Phase 10: Dashcam upload memory/size fixes — ✅ DONE, verified on real hardware Two real-hardware-only bugs surfaced once actual ~300MB+ video files (not small test fixtures) hit the upload path for the first time: