Problem or opportunity
The web server currently has no authentication. While localhost-only binding (127.0.0.1) is the primary security control, Docker/K8s deployments bind to 0.0.0.0 — exposing the API to anyone on the network. After adding session CRUD and zsh terminal endpoints, an unauthenticated API becomes a high-risk attack surface (full shell access to the host).
Proposed solution
Add an optional API key authentication middleware for non-localhost deployments:
gscroll serve --api-key <key> flag (or GUILD_SCROLL_API_KEY env var).
- When set, every request must include
Authorization: Bearer <key> header.
- Requests without/with wrong key receive
401 Unauthorized (JSON body).
127.0.0.1 requests optionally bypass auth when --allow-localhost-bypass flag is set.
- API key generated as a cryptographically random 32-byte hex string (
secrets.token_hex(32)).
Scope
In scope: Bearer token middleware, CLI flag + env var support, 401 response, localhost bypass option.
Out of scope: JWT, OAuth, multi-user RBAC (future milestone), TLS (separate issue).
Acceptance criteria
Problem or opportunity
The web server currently has no authentication. While localhost-only binding (
127.0.0.1) is the primary security control, Docker/K8s deployments bind to0.0.0.0— exposing the API to anyone on the network. After adding session CRUD and zsh terminal endpoints, an unauthenticated API becomes a high-risk attack surface (full shell access to the host).Proposed solution
Add an optional API key authentication middleware for non-localhost deployments:
gscroll serve --api-key <key>flag (orGUILD_SCROLL_API_KEYenv var).Authorization: Bearer <key>header.401 Unauthorized(JSON body).127.0.0.1requests optionally bypass auth when--allow-localhost-bypassflag is set.secrets.token_hex(32)).Scope
In scope: Bearer token middleware, CLI flag + env var support, 401 response, localhost bypass option.
Out of scope: JWT, OAuth, multi-user RBAC (future milestone), TLS (separate issue).
Acceptance criteria
--api-keyflag andGUILD_SCROLL_API_KEYenv var acceptedhmac.compare_digest)