diff --git a/guide/security.md b/guide/security.md index e181e65..f5f2819 100644 --- a/guide/security.md +++ b/guide/security.md @@ -68,6 +68,8 @@ Soft-deleted vault items remain as **tombstones** until peers sync; last-write-w - Set a strong unique `JWT_SECRET` (min 32 chars; placeholders rejected at startup). - Terminate **HTTPS** in front of the API in production. - Keep `CORS_ORIGINS` an explicit allow-list (**never `*`**). +- When running multiple API workers, add **reverse-proxy rate limits** — in-process auth limits are per worker (see [Install the server](./server#production-hardening)). +- Prefer HTTPS with a valid certificate; clients do not pin certs — HTTP or bad TLS enables MITM on sync/login. - Prefer keeping the vault **locked when idle**; enable biometric unlock carefully. - Use **Settings → Security → Password health** to find weak/reused passwords; optional HIBP checks send only a SHA-1 hash **prefix** (k-anonymity), never the password. - Treat exports / backups as secret material — store offline and encrypted. diff --git a/guide/server.md b/guide/server.md index 9c8ac81..35cd79d 100644 --- a/guide/server.md +++ b/guide/server.md @@ -80,6 +80,32 @@ CORS_ORIGINS=https://openkey.example.com Point clients at `https://openkey.example.com` (no port). Confirm `https://openkey.example.com/health`. +## Production hardening + +Beyond TLS and CORS, self-hosters should plan for **multi-worker deployments** and **client trust**. + +### Reverse proxy and TLS + +- Terminate **HTTPS** at Caddy, nginx, or your load balancer. Do not expose Postgres or the raw API port publicly. +- Add **HSTS** at the proxy (`Strict-Transport-Security`) so browsers never fall back to HTTP after the first visit. +- Set `TRUST_PROXY_HEADERS=true` only when the proxy overwrites `X-Forwarded-For` and you trust that path; otherwise per-IP rate limits follow the proxy, not the end user. + +### Rate limiting + +The API applies an in-memory sliding-window limiter on auth endpoints (`AUTH_RATE_LIMIT_*` in `.env`). That limit is **per Uvicorn worker process**. With multiple workers or replicas, effective limits multiply unless you add a shared limiter at the reverse proxy (for example `limit_req` in nginx or Caddy rate limits). + +### Email enumeration tradeoffs + +`POST /auth/prelogin` and `POST /auth/lookup-public-key` return **404** when the email is unknown. That helps legitimate clients (extension login, org key wrapping) but lets an attacker probe which emails are registered. Mitigations: + +- Keep auth rate limits strict at the proxy and API. +- Do not expose the sync API on untrusted networks without TLS. +- For high-sensitivity deployments, place the API behind VPN or IP allow-lists. + +### Client MITM on self-hosted URLs + +The OpenKey app, extension, and CLI use the platform TLS stack with **no certificate pinning** by default. Users who type `http://` or accept misconfigured certificates are vulnerable to MITM during login and sync. Always serve HTTPS with a valid certificate and document the server URL for your users. + ## API overview Interactive OpenAPI: `http://localhost:8000/docs` on a running server. Full tables live in the `openkey_server` package README. Highlights: