Administration
Security model & hardening
How Homelab Hand protects credentials and sessions, and a hardening checklist.
The hub holds root-equivalent access to everything it manages: Docker on every environment (which is root on those hosts), SSH keys, Proxmox tokens and NAS credentials. Treat it like a password manager.
What's protected, and how
| Asset | Protection |
|---|---|
| User passwords | Argon2id hashes. |
| Sessions | Random 256-bit tokens, stored only as their SHA-256. HttpOnly, SameSite=Strict, Secure on HTTPS. 12-hour lifetime; sessions waiting for a 2-step code last 15 minutes. |
| 2-step verification | TOTP (±1 step) and WebAuthn passkeys. Passwordless passkey sign-in requires user verification. Login is rate-limited (10 failures per 15 minutes per IP). |
| Google / Apple sign-in | Only accounts already linked to a hub user; state, nonce and PKCE; single-use 10-minute links; 2-step still required. |
| API tokens | Shown once, stored as SHA-256, scoped read/write, tied to their user. They can't manage users or tokens. |
| Stored credentials | SSH keys, passwords, Proxmox / UniFi / Portainer / Hawser tokens, app integration keys and DSM device tokens are encrypted (Fernet, key derived from HUB_SECRET_KEY). |
| Agent tokens | Edge tokens stored as hashes; standard-mode tokens encrypted (the hub must present them). |
| SSH and NAS certificates | Host keys and self-signed certificates are pinned on first use. A change blocks the connection before any password is sent. |
| Web UI | CSP script-src 'self' (all JavaScript vendored), X-Frame-Options: DENY, nosniff. State-changing requests from other web origins are refused. |
| Apps gateway | Separate ports from the hub, private addresses only, short-lived passes, hub cookies never forwarded, and a kill switch for remote access. |
| App integrations | Definitions can only call paths on their own app; same-origin redirects only; 4 MB response cap. |
| Shell injection | Container IDs, image refs, stack and network names are validated, and shell arguments are quoted. |
| Audit | Container and host actions, terminals, deploys, renames, integration actions and AI actions are logged as audit.action / ai.* events. |
| Configuration backups | AES-256-GCM with a scrypt-derived key from your passphrase; Google Drive access limited to the hub's own files. |
Hardening checklist
- ☐ Serve over HTTPS behind a reverse proxy or tunnel, and set
HUB_ORIGIN/HUB_RP_ID. - ☐ Set
HUB_TRUSTED_PROXIESto your proxy or tunnel connector. - ☐ Create the admin account immediately after install.
- ☐ Turn on TOTP and/or a passkey for every admin.
- ☐ Use viewer accounts and read API tokens wherever possible.
- ☐ Back up
HUB_SECRET_KEYand your backup passphrase somewhere other than the server. - ☐ Prefer TLS for the Docker API and Hawser Standard. Never expose
tcp://:2375. - ☐ Use
wss://for agents across untrusted networks. - ☐ Keep AI watchers on observe or approve until you trust them, and leave AI may run host commands off.
- ☐ Only enable the privileged helper where you need OS updates without SSH or the agent.
- ☐ Use a separate, dedicated account for Synology SSH restarts.
- ☐ Don't expose the hub to the internet without a VPN or an identity-aware proxy (e.g. Cloudflare Access) in front of it.
- ☐ Switch External app access off when you don't need remote apps.
Known limitations
- Passkeys need a hostname with HTTPS (or
localhost), not a bare IP. They aren't available inside the mobile app. - Docker socket access (the local test compose and
deploy.sh) is root-equivalent on that host, as with Portainer and Dockhand. - Synology Secure SignIn push approvals can't be answered over the API, so use an OTP method.
- Plain-HTTP access on the LAN (
http://ip:port) can't hold a Secure cookie once the hub is configured for https. Sign in through the https address.
Reverse proxies and client IPs
Set HUB_TRUSTED_PROXIES (comma-separated IPs or CIDRs) to your reverse proxy or tunnel connector. Only requests from those addresses may set X-Forwarded-For; from anyone else it's ignored. Without this, every proxied visitor looks like the proxy, so login limits and "new sign-in IP" alerts apply to the proxy as a whole.