Plan K1 / spec §15.7 / §16 Phase 5.
The daemon now ships a two-part hosted-mode safety floor:
1. startServer() refuses to start when OD_BIND_HOST is set to a
non-loopback address and OD_API_TOKEN is unset. Loopback hosts
(127.0.0.1 / ::1 / localhost) keep the existing zero-config
desktop / dev experience. The error message points the operator
at `openssl rand -hex 32` so the next attempt succeeds in one
step.
2. When OD_API_TOKEN is set, every /api/* request must carry an
`Authorization: Bearer <OD_API_TOKEN>` header. Three escape
hatches:
- Loopback peers (req.socket.remoteAddress matches
isLoopbackPeerAddress) skip the check — the desktop UI / local
CLI never need a bearer.
- The /api/health, /api/version, and /api/daemon/status probes
remain open so monitoring + cloud orchestrators (k8s readiness,
Compose healthcheck) work without touching secrets.
- Mismatched / missing headers return 401 API_TOKEN_REQUIRED.
This closes a previously documented Phase 5 hazard: an operator
spinning up the daemon with OD_BIND_HOST=0.0.0.0 (the docker-compose
default) without a token would have published an unsecured API.
Daemon tests: 1486 → 1490 (+4 cases on api-token-guard:
non-loopback bind without token rejected, public host with token
boots, loopback callers still pass without bearer, probes stay open).
Co-authored-by: Tom Huang <1043269994@qq.com>