Skip to main content

0008. Public health check, authenticated status

  • Status: Accepted
  • Date: 2026-09-30

Context

In Phase 0, GET /api/health was public and returned the backend version, whether trading was enabled, and the geoblock result with country and region. The Phase 0 security review (finding F20) noted that anyone who can reach the server could learn:
  • the software version, which helps target known flaws;
  • whether live trading is on, which marks the host as worth attacking;
  • where the server is, from the geoblock country and region.
A public endpoint is still needed. A container runtime, a reverse proxy or an uptime monitor has to check that the process is up without holding a session.

Decision

  • GET /api/health stays public and returns only {"status": "ok" | "degraded"} with HTTP 200. When the backend reports degraded is defined by implementation.
  • GET /api/status requires a session and returns version, trading_enabled, geoblock and feeds. It is a GET, so it needs no CSRF token.
  • The same Status object is sent on the authenticated status WebSocket topic.
  • No other public endpoint returns version, trading state, location, account or feed details.
This moves the fields that ADR 0005 said were on /api/health.

Consequences

  • An unauthenticated visitor learns only that the service is up and whether it is degraded.
  • Liveness checks keep working without credentials. A check must read the body to tell ok from degraded, because both return 200.
  • Checking the geoblock result or trading_enabled needs a login: through the UI’s status indicator, or by opening /api/status in the logged-in browser. curl against /api/health no longer shows them.
  • The UI reads status from the WebSocket topic and the REST endpoint, which serve the same state.

Alternatives considered

  • Keep all details on the public endpoint. Rejected: finding F20.
  • Require a session for /health as well. Rejected. Liveness checks would need a credential, and a stored session for a monitor is a new secret to protect.
  • Show details only to requests from localhost. Rejected. Behind the reverse proxy every request arrives from localhost, so the details would be public again; the backend does not trust proxy headers to find the real client.