Skip to content

ws: /ws/telemetry accepts unauthenticated connections; README claims 1008 rejection #27

Description

@masseselsev

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).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions