Skip to content

fix: add /api/ws WebSocket endpoint to APIServerAdapter for remote TUI attach (hardened) - #32955

Closed
trevor-ai-assistant wants to merge 1 commit into
NousResearch:mainfrom
trevor-ai-assistant:fix/tui-gateway-ws-attach-hardened
Closed

trevor-ai-assistant wants to merge 1 commit into
NousResearch:mainfrom
trevor-ai-assistant:fix/tui-gateway-ws-attach-hardened

Conversation

@trevor-ai-assistant

Copy link
Copy Markdown

Summary

This PR fixes Issue #32882: the documented HERMES_TUI_GATEWAY_URL workflow for attaching a local TUI to a remote gateway fails with HTTP 404 because the gateway's APIServerAdapter (aiohttp) never registers a WebSocket route at /api/ws. The route only exists in the separate FastAPI web dashboard server.

Changes

gateway/platforms/api_server.py

  • Import guard for tui_gateway.server and Transport (graceful degradation when TUI gateway is unavailable)
  • New endpoint GET /api/ws — registered only when TUI_GATEWAY_AVAILABLE is True
  • _handle_tui_ws — full WebSocket lifecycle handler with:
    • Authentication via ?token= query parameter using hmac.compare_digest (same API_SERVER_KEY as REST endpoints)
    • Defense-in-depth network guard — refuses WS connections on network-accessible hosts without a key, even if the startup guard is bypassed
    • Resource limitsheartbeat=30.0, autoping=True, max_msg_size=2*1024*1024 (2MB cap)
    • _AiohttpWsTransport adapter — bridges aiohttp's WebSocketResponse to the tui_gateway.Transport protocol so server.dispatch() works over WebSocket
    • gateway.ready handshake — TUI expects this before any RPC
    • JSON-RPC dispatch loop — mirrors tui_gateway.ws.handle_ws logic
    • Clean teardown — transport binding reset, connection closed in finally

tests/gateway/test_api_server_ws.py (new)

9 tests covering:

  • WS auth rejection (wrong token, missing token)
  • WS auth success (correct token)
  • No-auth localhost (allowed)
  • No-auth network defense-in-depth (403)
  • gateway.ready handshake
  • JSON-RPC parse error
  • JSON-RPC unknown method dispatch
  • Graceful disconnect

Test Results

tests/gateway/test_api_server_ws.py     — 9 passed
tests/gateway/test_api_server.py        — 150 passed (zero regressions)

Security Hardening (vs. PR #32904)

This implementation supersedes PR #32904 with critical security fixes:

Aspect PR #32904 This PR
Auth ❌ Missing entirely ?token= via hmac.compare_digest
max_msg_size ❌ No limit ✅ 2 MB cap
heartbeat ❌ No keepalive ✅ 30s heartbeat
autoping ❌ Missing ✅ Auto-pong
Network endpoint guard ❌ None ✅ Defense-in-depth 403
Tests ❌ None ✅ 9 comprehensive tests

Without authentication, PR #32904 would allow any network client to open a WebSocket and execute JSON-RPC commands (shell.exec, cli.exec, etc.) on a gateway with API_SERVER_KEY configured — rendering the REST API auth useless while leaving the WS schiene wide open.

Checklist

  • Bug fix (top priority per CONTRIBUTING.md)
  • Security hardening
  • Cross-platform compatibility maintained
  • No new dependencies
  • Tests match existing pytest style
  • Zero regressions (150 existing tests passing)
  • Docstring updated
  • Type hints present

Related

…I attach

The TUI frontend (HERMES_TUI_GATEWAY_URL) expects to connect to
ws://host:port/api/ws?token=... but the APIServerAdapter (aiohttp)
only registers HTTP routes — no WebSocket handler at /api/ws, causing
HTTP 404 on remote TUI attach.

This patch hardens the approach from PR NousResearch#32904 with security fixes:

- Adds try/except import guard for tui_gateway.server and Transport
- Registers GET /api/ws route on the aiohttp web.Application (only when
  tui_gateway is available)
- Implements _handle_tui_ws with an _AiohttpWsTransport adapter that
  bridges aiohttp's WebSocketResponse to the Transport protocol so
  tui_gateway.server.dispatch() works over WebSocket
- Authentication via ?token= query parameter using hmac.compare_digest
  (same API_SERVER_KEY as REST endpoints)
- Defense-in-depth: refuses network-accessible connections without key
  at the endpoint level (in addition to existing startup guard)
- Resource limits: heartbeat=30.0, autoping=True, max_msg_size=2MB
- Sends gateway.ready event on connect, then runs JSON-RPC dispatch loop
- Cleanly tears down transport binding on disconnect

Closes NousResearch#32882
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery comp/tui Terminal UI (ui-tui/ + tui_gateway/) labels May 27, 2026
@alt-glitch

Copy link
Copy Markdown
Contributor

Competing with #32904 — both fix #32882 (missing /api/ws WebSocket route). This PR adds security hardening (HMAC auth, network defense-in-depth, resource limits) and tests. Supersedes #32904.

@teknium1

teknium1 commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Closing this — the fix targets the wrong API surface.

APIServerAdapter (gateway/platforms/api_server.py) is the OpenAI-compatible endpoint for using a Hermes instance as a model backend (/v1/chat/completions, /v1/models, /v1/responses, etc.). It was never intended to carry the TUI's JSON-RPC control channel — that channel exposes shell.exec, cli.exec, and full agent control, which has a fundamentally different trust model from "talk to my Hermes as an OpenAI endpoint." Adding /api/ws there is the wrong surface regardless of how well it's authed.

/api/ws legitimately lives only in the dashboard server (hermes_cli/web_server.py), which spawns its own embedded TUI child and injects HERMES_TUI_GATEWAY_URL as an internal wiring detail (one in-process FastAPI server, its own PTY child). It is not a public "point any TUI at any gateway port" knob.

The real bug behind #32882 is misleading documentation — website/docs/user-guide/tui.md presented HERMES_TUI_GATEWAY_URL as a remote-attach workflow against the gateway, which it isn't. We're fixing the docs instead.

The security analysis here was genuinely good — you correctly identified that #32904 would have left an unauthenticated RPC channel wide open. Thanks for the careful work; it just lands on a surface we don't want to expose this on.

@teknium1 teknium1 closed this Jun 8, 2026
teknium1 added a commit that referenced this pull request Jun 8, 2026
…emote-attach (#42162)

The TUI docs presented HERMES_TUI_GATEWAY_URL + /api/ws as a supported
'attach the TUI to a standalone running gateway' workflow. It isn't.

/api/ws exists only inside the dashboard's FastAPI server
(hermes_cli/web_server.py), which spawns its own embedded TUI child and
injects the var as an internal wiring detail. The OpenAI-compat API
server (api_server platform) deliberately does not serve /api/ws, so the
documented ws://host:port/api/ws workflow 404s — the cause of #32882 and
the two PRs (#32904, #32955) that tried to add the route to the wrong
surface.

Rewrites the section in en + zh-Hans to describe the var accurately and
point users at shared state.db / dashboard embedded chat for multi-surface
session sharing.
changman pushed a commit to changman/hermes-agent that referenced this pull request Jun 10, 2026
…emote-attach (NousResearch#42162)

The TUI docs presented HERMES_TUI_GATEWAY_URL + /api/ws as a supported
'attach the TUI to a standalone running gateway' workflow. It isn't.

/api/ws exists only inside the dashboard's FastAPI server
(hermes_cli/web_server.py), which spawns its own embedded TUI child and
injects the var as an internal wiring detail. The OpenAI-compat API
server (api_server platform) deliberately does not serve /api/ws, so the
documented ws://host:port/api/ws workflow 404s — the cause of NousResearch#32882 and
the two PRs (NousResearch#32904, NousResearch#32955) that tried to add the route to the wrong
surface.

Rewrites the section in en + zh-Hans to describe the var accurately and
point users at shared state.db / dashboard embedded chat for multi-surface
session sharing.
alt-glitch pushed a commit that referenced this pull request Jun 14, 2026
…emote-attach (#42162)

The TUI docs presented HERMES_TUI_GATEWAY_URL + /api/ws as a supported
'attach the TUI to a standalone running gateway' workflow. It isn't.

/api/ws exists only inside the dashboard's FastAPI server
(hermes_cli/web_server.py), which spawns its own embedded TUI child and
injects the var as an internal wiring detail. The OpenAI-compat API
server (api_server platform) deliberately does not serve /api/ws, so the
documented ws://host:port/api/ws workflow 404s — the cause of #32882 and
the two PRs (#32904, #32955) that tried to add the route to the wrong
surface.

Rewrites the section in en + zh-Hans to describe the var accurately and
point users at shared state.db / dashboard embedded chat for multi-surface
session sharing.
davidgut1982 pushed a commit to davidgut1982/hermes-agent that referenced this pull request Jun 17, 2026
…emote-attach (NousResearch#42162)

The TUI docs presented HERMES_TUI_GATEWAY_URL + /api/ws as a supported
'attach the TUI to a standalone running gateway' workflow. It isn't.

/api/ws exists only inside the dashboard's FastAPI server
(hermes_cli/web_server.py), which spawns its own embedded TUI child and
injects the var as an internal wiring detail. The OpenAI-compat API
server (api_server platform) deliberately does not serve /api/ws, so the
documented ws://host:port/api/ws workflow 404s — the cause of NousResearch#32882 and
the two PRs (NousResearch#32904, NousResearch#32955) that tried to add the route to the wrong
surface.

Rewrites the section in en + zh-Hans to describe the var accurately and
point users at shared state.db / dashboard embedded chat for multi-surface
session sharing.
T02200059 pushed a commit to T02200059/hermes-agent that referenced this pull request Jun 18, 2026
…emote-attach (NousResearch#42162)

The TUI docs presented HERMES_TUI_GATEWAY_URL + /api/ws as a supported
'attach the TUI to a standalone running gateway' workflow. It isn't.

/api/ws exists only inside the dashboard's FastAPI server
(hermes_cli/web_server.py), which spawns its own embedded TUI child and
injects the var as an internal wiring detail. The OpenAI-compat API
server (api_server platform) deliberately does not serve /api/ws, so the
documented ws://host:port/api/ws workflow 404s — the cause of NousResearch#32882 and
the two PRs (NousResearch#32904, NousResearch#32955) that tried to add the route to the wrong
surface.

Rewrites the section in en + zh-Hans to describe the var accurately and
point users at shared state.db / dashboard embedded chat for multi-surface
session sharing.
waefrebeorn pushed a commit to waefrebeorn/slermes that referenced this pull request Jul 2, 2026
…emote-attach (NousResearch#42162)

The TUI docs presented HERMES_TUI_GATEWAY_URL + /api/ws as a supported
'attach the TUI to a standalone running gateway' workflow. It isn't.

/api/ws exists only inside the dashboard's FastAPI server
(hermes_cli/web_server.py), which spawns its own embedded TUI child and
injects the var as an internal wiring detail. The OpenAI-compat API
server (api_server platform) deliberately does not serve /api/ws, so the
documented ws://host:port/api/ws workflow 404s — the cause of NousResearch#32882 and
the two PRs (NousResearch#32904, NousResearch#32955) that tried to add the route to the wrong
surface.

Rewrites the section in en + zh-Hans to describe the var accurately and
point users at shared state.db / dashboard embedded chat for multi-surface
session sharing.
santhreal pushed a commit to santhreal/hermes-agent that referenced this pull request Jul 13, 2026
…emote-attach (NousResearch#42162)

The TUI docs presented HERMES_TUI_GATEWAY_URL + /api/ws as a supported
'attach the TUI to a standalone running gateway' workflow. It isn't.

/api/ws exists only inside the dashboard's FastAPI server
(hermes_cli/web_server.py), which spawns its own embedded TUI child and
injects the var as an internal wiring detail. The OpenAI-compat API
server (api_server platform) deliberately does not serve /api/ws, so the
documented ws://host:port/api/ws workflow 404s — the cause of NousResearch#32882 and
the two PRs (NousResearch#32904, NousResearch#32955) that tried to add the route to the wrong
surface.

Rewrites the section in en + zh-Hans to describe the var accurately and
point users at shared state.db / dashboard embedded chat for multi-surface
session sharing.
donbowman pushed a commit to donbowman/hermes-agent that referenced this pull request Jul 13, 2026
…emote-attach (NousResearch#42162)

The TUI docs presented HERMES_TUI_GATEWAY_URL + /api/ws as a supported
'attach the TUI to a standalone running gateway' workflow. It isn't.

/api/ws exists only inside the dashboard's FastAPI server
(hermes_cli/web_server.py), which spawns its own embedded TUI child and
injects the var as an internal wiring detail. The OpenAI-compat API
server (api_server platform) deliberately does not serve /api/ws, so the
documented ws://host:port/api/ws workflow 404s — the cause of NousResearch#32882 and
the two PRs (NousResearch#32904, NousResearch#32955) that tried to add the route to the wrong
surface.

Rewrites the section in en + zh-Hans to describe the var accurately and
point users at shared state.db / dashboard embedded chat for multi-surface
session sharing.
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
…emote-attach (NousResearch#42162)

The TUI docs presented HERMES_TUI_GATEWAY_URL + /api/ws as a supported
'attach the TUI to a standalone running gateway' workflow. It isn't.

/api/ws exists only inside the dashboard's FastAPI server
(hermes_cli/web_server.py), which spawns its own embedded TUI child and
injects the var as an internal wiring detail. The OpenAI-compat API
server (api_server platform) deliberately does not serve /api/ws, so the
documented ws://host:port/api/ws workflow 404s — the cause of NousResearch#32882 and
the two PRs (NousResearch#32904, NousResearch#32955) that tried to add the route to the wrong
surface.

Rewrites the section in en + zh-Hans to describe the var accurately and
point users at shared state.db / dashboard embedded chat for multi-surface
session sharing.
leewenjie pushed a commit to leewenjie/hermes-agent that referenced this pull request Aug 7, 2026
…emote-attach (NousResearch#42162)

The TUI docs presented HERMES_TUI_GATEWAY_URL + /api/ws as a supported
'attach the TUI to a standalone running gateway' workflow. It isn't.

/api/ws exists only inside the dashboard's FastAPI server
(hermes_cli/web_server.py), which spawns its own embedded TUI child and
injects the var as an internal wiring detail. The OpenAI-compat API
server (api_server platform) deliberately does not serve /api/ws, so the
documented ws://host:port/api/ws workflow 404s — the cause of NousResearch#32882 and
the two PRs (NousResearch#32904, NousResearch#32955) that tried to add the route to the wrong
surface.

Rewrites the section in en + zh-Hans to describe the var accurately and
point users at shared state.db / dashboard embedded chat for multi-surface
session sharing.
melon-xf added a commit to melon-xf/hermes-agent that referenced this pull request Sep 3, 2026
…emote-attach (NousResearch#42162)

The TUI docs presented HERMES_TUI_GATEWAY_URL + /api/ws as a supported
'attach the TUI to a standalone running gateway' workflow. It isn't.

/api/ws exists only inside the dashboard's FastAPI server
(hermes_cli/web_server.py), which spawns its own embedded TUI child and
injects the var as an internal wiring detail. The OpenAI-compat API
server (api_server platform) deliberately does not serve /api/ws, so the
documented ws://host:port/api/ws workflow 404s — the cause of NousResearch#32882 and
the two PRs (NousResearch#32904, NousResearch#32955) that tried to add the route to the wrong
surface.

Rewrites the section in en + zh-Hans to describe the var accurately and
point users at shared state.db / dashboard embedded chat for multi-surface
session sharing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery comp/tui Terminal UI (ui-tui/ + tui_gateway/) P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TUI remote gateway attach (HERMES_TUI_GATEWAY_URL) fails with 404 — /api/ws missing from APIServerAdapter

3 participants