IKHSAN
EN Read in EnglishID

homeio

Publik

Fork dari doctor-io/homeio

Branch
main
Commit terakhir
958d74616 Oktober 2026

security.md

Buka raw
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

GapSeverityExploit scenarioStatus
Missing middleware-level authMediumA future route could bypass auth if it is outside the architecture test scopeMitigated by requireApiSession() and architecture test
Login rate limiting is in-memoryMediumDistributed deployments would not share brute-force counters across processesMitigated for single-process installs
Unsafe production AUTH_SESSION_SECRETHighUsers who do not change it risk forged session tokensMitigated at runtime by lib/server/env.ts; development/test defaults still work
Docker socket mountedHighApp compromise can become host compromise through DockerAccepted deployment trade-off
WebSocket terminal is full shellHighAuthenticated terminal access is host shell access, not command allowlistOpen documentation gap
Upload sidecar socket is broad-permissionMediumLocal host users could try authenticated upload requests through the socketMitigated by session validation and path jail; keep socket local
Disk manager destructive routesHighAuthenticated user can format, partition, or wipe host disksMitigated by single-user trust model and explicit UI confirmations
passwordHash returned from session lookupLowHash circulates in memory unnecessarilyOpen
services/dbus-helper/ plain JSMediumLower type safety for privileged sidecar protocolOpen

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.
byte
5.782
baris
71
Jenis
md
Commit terakhir
16 Oktober 2026