Discovered while auditing the bootstrap-frame PR (#26).
The gap. README states: "Real-time WebSocket telemetry (/ws/telemetry) inspects handshake cookies or ?token= and rejects unauthenticated connections with code 1008 (Policy Violation) before scheduling ticks." The Go core does not implement this: internal/api/router.go mounts /ws/telemetry outside the /api/v1 AuthMiddleware subtree, and Hub.HandleWS (internal/api/ws.go) never checks mikroman_session, ?token=, or the Authorization header. AuthMiddleware itself only guards /api/v1/* paths — /ws/telemetry is not under it. Any host that can reach the port can open a socket and receive every router's live telemetry (per-user rates, WAN IPs, device counts), and after #26 also history-summary frames.
The exemption path == "/api/v1/ws" in AuthMiddleware hints where the endpoint lived in the Python core; the Go rewrite appears to have dropped the check.
Fix sketch.
- In
HandleWS (or a wrapper middleware), before upgrader.Upgrade: extract session cookie or ?token=, verify via the same Fernet path as AuthMiddleware; when cfg.AuthEnabled and verification fails, send websocket.CloseMessage(websocket.ClosePolicyViolation /* 1008 */, ...) and return.
- The hub needs access to config+fernet — pass them into NewHub, or wrap the route with an HTTP middleware that 401s before the upgrade (note browsers report such a rejection as a generic socket error; the 1008-close path keeps the README promise and gives the frontend a clean signal to stop reconnecting —
useWebSocketTelemetry already special-cases 1008).
- Add tests: unauthenticated dial is closed with 1008; authenticated dial receives
connected; token-in-query dial works (the app does not currently send it, but the README contract includes it).
Severity: high for any deployment where port 1928 is reachable beyond localhost/LAN (the dstnat rule is deliberately br.lan only on RouterOS, but plain-Docker installs have no such guard).
Discovered while auditing the bootstrap-frame PR (#26).
The gap. README states: "Real-time WebSocket telemetry (
/ws/telemetry) inspects handshake cookies or?token=and rejects unauthenticated connections with code 1008 (Policy Violation) before scheduling ticks." The Go core does not implement this:internal/api/router.gomounts/ws/telemetryoutside the/api/v1AuthMiddleware subtree, andHub.HandleWS(internal/api/ws.go) never checksmikroman_session,?token=, or the Authorization header.AuthMiddlewareitself only guards/api/v1/*paths —/ws/telemetryis not under it. Any host that can reach the port can open a socket and receive every router's live telemetry (per-user rates, WAN IPs, device counts), and after #26 also history-summary frames.The exemption
path == "/api/v1/ws"in AuthMiddleware hints where the endpoint lived in the Python core; the Go rewrite appears to have dropped the check.Fix sketch.
HandleWS(or a wrapper middleware), beforeupgrader.Upgrade: extract session cookie or?token=, verify via the same Fernet path as AuthMiddleware; whencfg.AuthEnabledand verification fails, sendwebsocket.CloseMessage(websocket.ClosePolicyViolation /* 1008 */, ...)and return.useWebSocketTelemetryalready special-cases 1008).connected; token-in-query dial works (the app does not currently send it, but the README contract includes it).Severity: high for any deployment where port 1928 is reachable beyond localhost/LAN (the dstnat rule is deliberately
br.lan onlyon RouterOS, but plain-Docker installs have no such guard).