WebSockets Information and Design
PatchMon WebSockets
This document describes how WebSockets are used in PatchMon: endpoints, authentication, WS vs WSS behaviour, and security.
Overview
PatchMon uses a single HTTP server with noServer: true WebSocket handling. All WebSocket upgrades are handled in one place (server.on("upgrade")) and routed by path:
| Path pattern | Purpose | Clients |
|---|---|---|
/api/{v}/agents/ws |
Agent ↔ server persistent connection | PatchMon agent (Go) |
/api/{v}/ssh-terminal/:hostId |
Browser SSH terminal to a host | Frontend (browser) |
/bullboard* |
Bull Board queue UI real-time updates | Bull Board (browser) |
Implementation lives in:
- Backend:
backend/src/services/agentWs.js(agent + Bull Board),backend/src/services/sshTerminalWs.js(SSH terminal).
WebSocket Endpoints
Agent WebSocket
- Path:
/api/{version}/agents/ws(e.g./api/v1/agents/ws) - Auth: HTTP headers on the upgrade request:
X-API-ID,X-API-KEY(validated againsthostsand API key utils). - Purpose: Persistent connection from each agent to the server for:
- Heartbeat / presence
- Commands from server (e.g. trigger update, compliance scan)
- Events from agent (e.g. Docker status)
SSH Terminal WebSocket
- Path:
/api/{version}/ssh-terminal/:hostId(e.g./api/v1/ssh-terminal/abc-123) - Auth: One-time ticket (query
ticket=...) or legacy JWT (token=...orAuthorization: Bearer ...). User must havecan_manage_hosts(or admin). - Purpose: Browser opens a WebSocket to the backend; backend either connects directly to the host via SSH or proxies through the agent WebSocket to the host.
Bull Board WebSocket
- Path:
/bullboard(prefix match) - Auth: Session cookie
bull-board-sessionorAuthorizationheader. - Purpose: Real-time updates for the Bull Board queue UI (echo-style).
WS vs WSS (Secure vs Insecure)
How each part of the system decides between ws (insecure) and wss (secure):
Server (backend)
The server does not choose the protocol; it detects whether the incoming connection is secure and records it for logging and metadata:
socket.encrypted—truewhen the TCP socket is TLS (direct wss to Node).request.headers["x-forwarded-proto"] === "https"—truewhen TLS is terminated at a reverse proxy (e.g. Nginx) that sends the original protocol.
So: if the client connects over TLS, or the proxy sets X-Forwarded-Proto: https, the server treats the connection as secure (wss). Otherwise it is treated as ws.
Code: backend/src/services/agentWs.js (e.g. isSecure, connectionMetadata, log line with protocol=wss|ws).
Agent (Go)
The agent converts the configured server URL to a WebSocket URL and uses that to connect:
| Configured URL | WebSocket URL |
|---|---|
https://... |
wss://... |
http://... |
ws://... |
Already wss:// or ws:// |
Used as-is |
| No protocol | Assumed HTTPS → wss://... |
The path is then appended: .../api/{version}/agents/ws.
Code: agent-source-code/cmd/patchmon-agent/commands/serve.go (connectOnce, URL conversion and wsURL).
Frontend (browser)
The frontend (e.g. SSH terminal) builds the WebSocket URL so it matches the page:
- Page loaded over HTTPS →
wss: - Page loaded over HTTP →
ws:
So the same app works on http and https without extra configuration.
Code: frontend/src/components/SshTerminal.jsx (e.g. protocol = window.location.protocol === "https:" ? "wss:" : "ws:").
Authentication
- Agent WS: Credentials only on the upgrade request via
X-API-IDandX-API-KEY. No auth in query or body. Keys are validated (including bcrypt/legacy) before the WebSocket is accepted. - SSH terminal: Prefer one-time ticket in query (
ticket=...); ticket is consumed once. Legacy:token=...orAuthorization: Bearer .... User must be authorized for the host (e.g.can_manage_hosts). - Bull Board: Session cookie or
Authorizationheader; required before accepting the upgrade.
All three paths reject the upgrade (e.g. 401) if auth fails; the WebSocket is never established.
Message Types and Flows
- Agent → Backend: e.g.
docker_status, pings. JSON over the agent WebSocket. - Backend → Agent: e.g.
update_agent,compliance_scan, SSH proxy payloads. Sent over the same WebSocket by api_id. - SSH terminal: Browser sends
connectwith SSH options; backend (or agent) establishes SSH and forwards terminal I/O over the WebSocket. Resize and other control messages are defined insshTerminalWs.jsand the frontend.
Security Notes
- TLS in production: Use HTTPS and WSS. The agent assumes WSS when the server URL is
https://or has no scheme. - Proxy: When behind Nginx (or similar), ensure
X-Forwarded-Proto: httpsis set for HTTPS so the backend correctly detects secure connections. - Agent TLS verification:
skip_ssl_verifyis blocked whenPATCHMON_ENV=production; avoid disabling TLS verification in production. - SSH terminal: Prefer one-time tickets; avoid putting long-lived tokens in URLs (logs, history). Permissions are enforced per user/role (
can_manage_hosts). - Brute force / abuse: Upgrade is rejected with 401/404 before the WebSocket is created; no WebSocket resource is exposed without valid auth.
No comments to display
No comments to display