# Security ## Threat Model Homeio is designed for a trusted LAN or a protected reverse-proxy deployment. It manages high-privilege host surfaces: Docker, files under `FILES_ROOT`, D-Bus network/power/storage actions, and terminal access. It is not currently hardened as an internet-facing multi-user service. Defended: - Session cookie tampering through HMAC-SHA256 signatures. - Password disclosure through scrypt password hashing. - File traversal outside `FILES_ROOT` through `resolvePathWithinFilesRoot()`. - Large upload traversal in the Go upload sidecar through its own `FILES_ROOT` path jail and session-cookie HMAC validation. - PTY environment leakage through a terminal env allowlist. Not fully defended: - Brute force attempts across multiple app processes; the current limiter is in-memory. - Middleware-level authorization for all `/api/v1/**` routes. - Exposure of Docker socket capabilities. ## Auth Mechanism `lib/server/modules/auth/session-token.ts` signs `{sessionId}.{expiresAtEpochSeconds}` with HMAC-SHA256 using `AUTH_SESSION_SECRET`. The token is stored in the `homeio_session` cookie from `lib/server/modules/auth/cookies.ts` with `httpOnly` and `sameSite: lax`; the `Secure` flag depends on HTTPS request context. Passwords are hashed in `lib/server/modules/auth/password.ts` using Node `crypto.scrypt` with a random salt. scrypt is a valid KDF, but argon2 is more common for new web password storage. The custom session format is simpler than JWT/PASETO and avoids token claims, but it is non-standard and relies on a strong `AUTH_SESSION_SECRET`. `app/api/auth/login/route.ts` uses the in-memory limiter in `lib/server/modules/auth/rate-limit.ts`. Failed attempts are keyed by normalized username plus client IP from `x-forwarded-for` or `x-real-ip`; the current limit is 5 failures per 15 minutes. A successful login clears the key. Limited requests return `429` with `{ "error": "Too many login attempts" }`. `lib/server/env.ts` rejects unsafe production `AUTH_SESSION_SECRET` values at runtime. In production, known defaults (`dev-session-secret-change-me`, `change-me-to-a-random-32-char-secret`) and secrets shorter than 32 characters fail startup, except during the Next production build phase where runtime secrets may not be present. ## Path Jailing `lib/server/modules/files/path-resolver.ts` exports `resolvePathWithinFilesRoot()`. File operations should pass all user-provided paths through this resolver before touching the filesystem. The file manager service maps `FilesPathError` to `FileServiceError`. ## Docker Socket Risk `docker-compose.yml` mounts `/var/run/docker.sock`. Access to the Docker socket is effectively host-root equivalent in many deployments. Any route or bug that can trigger arbitrary Docker commands is high severity. ## D-Bus Privilege Model Network and USB features use `services/dbus-helper/` over `DBUS_HELPER_SOCKET_PATH`. The sidecar talks to NetworkManager and udisks2 through D-Bus. It is a separate privilege boundary and must be tested separately when changed. ## Upload Sidecar Bare-metal installs build `services/upload-server/main.go` and run it through systemd on `UPLOAD_SERVER_ADDR`, defaulting to `/run/home-server/upload.sock`. The sidecar validates the `homeio_session` cookie using `AUTH_SESSION_SECRET`, rejects unsafe filenames and paths, writes only under `FILES_ROOT`, and exposes a simple `/health` endpoint. The socket is chmod `0666` so the app process can reach it; do not expose a TCP `UPLOAD_SERVER_ADDR` outside a trusted local boundary. ## Google OAuth Secrets Settings → Integrations stores Google OAuth client credentials in the singleton `settings` row. The client secret is encrypted with the existing file-secret encryption helper; the public settings API returns only client ID, redirect URI, and `hasSecret`. Connected Google Drive account access and refresh tokens are stored encrypted in `files_google_drive_tokens`. ## Known Gaps | Gap | Severity | Exploit scenario | Status | |---|---:|---|---| | Missing middleware-level auth | Medium | A future route could bypass auth if it is outside the architecture test scope | Mitigated by `requireApiSession()` and architecture test | | Login rate limiting is in-memory | Medium | Distributed deployments would not share brute-force counters across processes | Mitigated for single-process installs | | Unsafe production `AUTH_SESSION_SECRET` | High | Users who do not change it risk forged session tokens | Mitigated at runtime by `lib/server/env.ts`; development/test defaults still work | | Docker socket mounted | High | App compromise can become host compromise through Docker | Accepted deployment trade-off | | WebSocket terminal is full shell | High | Authenticated terminal access is host shell access, not command allowlist | Open documentation gap | | Upload sidecar socket is broad-permission | Medium | Local host users could try authenticated upload requests through the socket | Mitigated by session validation and path jail; keep socket local | | Disk manager destructive routes | High | Authenticated user can format, partition, or wipe host disks | Mitigated by single-user trust model and explicit UI confirmations | | `passwordHash` returned from session lookup | Low | Hash circulates in memory unnecessarily | Open | | `services/dbus-helper/` plain JS | Medium | Lower type safety for privileged sidecar protocol | Open | ## Mitigation Priorities 1. Move from per-route helper auth to middleware or generated route wrappers if feasible. 2. Consider persistent/distributed login rate limiting if Homeio ever runs multiple app processes. 3. Separate terminal UX/docs for allowlisted command execution vs full PTY. 4. Reduce Docker socket exposure where deployment topology allows it.