Skip to content

WebSockets

The Cloud Backend runs two WebSocket hubs for real-time push updates to the Cloud SPA:

PathPurpose
/api/ws/securitySecurity alert feed, investigation events
/api/ws/vpnVPN session status updates
sequenceDiagram
    participant SPA as Cloud SPA
    participant API as Cloud Backend
    participant Hub as WebSocket Hub

    SPA->>API: WS upgrade GET /api/ws/security?token=<access_jwt>
    API->>API: Validate JWT from query param
    API->>API: Extract tenant_id, user_id
    API->>Hub: Register connection { conn, tenant_id, user_id }
    API-->>SPA: 101 Switching Protocols
    Note over SPA,Hub: Connection open

    API->>Hub: Broadcast event to tenant_id connections
    Hub-->>SPA: WS message frame

    SPA->>API: WS close frame
    API->>Hub: Deregister connection

Authentication uses the JWT as a query parameter (?token=), not in headers. This is because the WebSocket API in browsers does not allow custom headers during the upgrade handshake. The ALLOW_ORIGINS env var is checked against the Origin header on the upgrade request.

The WebSocket hub lives in Backend/internal/websocket/hub.go. It maintains a concurrent map of:

tenant_id → []connection

Broadcasting works by looking up all connections for a given tenant_id and writing the event to each. Closed connections are cleaned up automatically.

When a security alert arrives (via webhook ingest, Ingestor event, or internal generation):

sequenceDiagram
    participant Source as Alert source (Ingestor / webhook / job)
    participant SecurityDomain as security domain
    participant SQL as Azure SQL
    participant Hub as WebSocket Hub
    participant SPA as Cloud SPA

    Source->>SecurityDomain: New alert payload
    SecurityDomain->>SQL: INSERT security_alerts
    SecurityDomain->>Hub: Broadcast({ type: "alert", data: alert })
    Hub-->>SPA: WS message (all tenant connections)
    SPA->>SPA: Update alert inbox in UI (TanStack Query invalidation)

The VPN hub broadcasts session state changes (connecting, connected, disconnected, error) to all SPAs open for the tenant. This allows multiple users to monitor VPN sessions in real-time without polling.

StoreFile
WebSocket stateFrontend/lib/stores/wsStore.ts
Security WS clientFrontend/lib/ws/securityWs.ts
VPN WS clientFrontend/lib/ws/vpnWs.ts

The Zustand wsStore tracks connection state (connecting | open | closed) and exposes methods to subscribe to events. TanStack Query cache is invalidated when relevant WS events arrive.

The Security module includes an investigation terminal (xterm.js in the SPA) that executes tools through VPN gateway connections. Tool execution results stream back via the /api/ws/security connection as incremental message frames.

The backend checks the Origin header on WS upgrade against ALLOW_ORIGINS. In production:

ALLOW_ORIGINS=https://cloud.monozu.io

In local dev:

ALLOW_ORIGINS=http://localhost:3000,http://127.0.0.1:3000