security(ws): /ws/realtime accepts unauthenticated connections #125

Open
opened 2026-09-25 22:57:54 +00:00 by gabogg · 0 comments
Owner

Security. High priority.

Problem

/ws/realtime on master (app/main.py, websocket_endpoint) accepts every connection with no authentication:

@app.websocket("/ws/realtime")
async def websocket_endpoint(websocket: WebSocket):
    await ws_manager.connect(websocket)

Anyone who can reach the server can open the socket and receive every broadcast. That includes live door states and transitions, access cycles (cardholder names and card numbers when the name resolves, #46), occupancy updates and system status, with no login.

Found during the pass-2 review of #123 (viewer role). #123 refuses a viewer with 1008 only when the viewer sends a token, so a viewer who leaves out the token still gets the stream, and so does an anonymous client.

Decisions

  • The WebSocket handshake requires a valid session, using the same session sources as HTTP: the hc_session cookie and/or the token the client already sends. With no session or an invalid one, the server closes with 1008 POLICY_VIOLATION before accepting.
  • Viewers are refused (1008), as #121 decided.
  • Admins and operators are unchanged.
  • Expired sessions: when a session expires while connected, close the socket on the next broadcast or keepalive, instead of keeping it open indefinitely.

Acceptance

  • Tests: an anonymous connection is refused; an invalid or expired token is refused; a viewer is refused; admin and operator connect and receive broadcasts.
  • The frontend telemetry engine still connects for admin and operator. It stops reconnecting after a 1008 close instead of retrying every 2 s (this overlaps with #123's viewer page fix).

Coordination

#123 is changing the same handler. Whichever lands second rebases onto the other. If #123 merges first, this issue removes the "only when a token is sent" gap it leaves.

🤖 Generated with Claude Code

**Security. High priority.** ## Problem `/ws/realtime` on `master` (`app/main.py`, `websocket_endpoint`) accepts **every** connection with no authentication: ```python @app.websocket("/ws/realtime") async def websocket_endpoint(websocket: WebSocket): await ws_manager.connect(websocket) ``` Anyone who can reach the server can open the socket and receive every broadcast. That includes live door states and transitions, access cycles (cardholder names and card numbers when the name resolves, #46), occupancy updates and system status, with no login. Found during the pass-2 review of #123 (viewer role). #123 refuses a viewer with 1008 only when the viewer sends a token, so a viewer who leaves out the token still gets the stream, and so does an anonymous client. ## Decisions - The WebSocket handshake **requires a valid session**, using the same session sources as HTTP: the `hc_session` cookie and/or the token the client already sends. With no session or an invalid one, the server closes with `1008 POLICY_VIOLATION` before accepting. - **Viewers are refused** (`1008`), as #121 decided. - Admins and operators are unchanged. - Expired sessions: when a session expires while connected, close the socket on the next broadcast or keepalive, instead of keeping it open indefinitely. ## Acceptance - Tests: an anonymous connection is refused; an invalid or expired token is refused; a viewer is refused; admin and operator connect and receive broadcasts. - The frontend telemetry engine still connects for admin and operator. It stops reconnecting after a 1008 close instead of retrying every 2 s (this overlaps with #123's viewer page fix). ## Coordination #123 is changing the same handler. Whichever lands second rebases onto the other. If #123 merges first, this issue removes the "only when a token is sent" gap it leaves. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
gabogg/hikcentral#125
No description provided.