Skip to content

fix: support proxied dashboard chat sessions - #21104

Open
hejun041 wants to merge 2 commits into
NousResearch:mainfrom
hejun041:fix/dashboard-ws-systemd-compat
Open

fix: support proxied dashboard chat sessions#21104
hejun041 wants to merge 2 commits into
NousResearch:mainfrom
hejun041:fix/dashboard-ws-systemd-compat

Conversation

@hejun041

@hejun041 hejun041 commented May 7, 2026

Copy link
Copy Markdown

Summary

  • remove systemd restart backoff keys that are unsupported by older systemd releases
  • make dashboard WebSocket handling work behind a trusted local reverse proxy
  • fix the TUI build freshness check to look for the actual built artifact (entry-exports.js)
  • surface recently active repeated-compression session chains on the dashboard /sessions page

Details

  • Drops RestartMaxDelaySec / RestartSteps from the generated gateway systemd unit so it works on older systemd versions (e.g. systemd 245).
  • Starts the dashboard Uvicorn server with proxy_headers=True and forwarded_allow_ips="127.0.0.1,::1" so local reverse proxies can pass through the original client/host information without trusting arbitrary remote forwarded headers.
  • Allows /api/pty WebSocket handshakes from a trusted local reverse proxy when the forwarded/Host header still matches the dashboard bind host, while preserving the DNS-rebinding guard for non-loopback clients.
  • Updates _hermes_ink_bundle_stale() to check ui-tui/packages/hermes-ink/dist/entry-exports.js instead of the removed ink-bundle.js, avoiding needless rebuilds during dashboard chat connection startup.
  • Fixes repeated context-compression chains so /api/sessions projects to the latest live tip even when an intermediate compression anchor itself has a parent, and sorts dashboard sessions by last activity.

Test Plan

  • venv/bin/python -m pytest tests/hermes_cli/test_gateway.py tests/hermes_cli/test_tui_npm_install.py tests/hermes_cli/test_web_server_host_header.py tests/hermes_cli/test_web_server.py::TestPtyWebSocket -q -o 'addopts='
  • venv/bin/python -m pytest tests/test_hermes_state.py::TestListSessionsRich tests/test_hermes_state.py::TestCompressionChainProjection tests/hermes_cli/test_web_server.py::TestPtyWebSocket tests/hermes_cli/test_web_server_host_header.py -q -o 'addopts='
  • Manual local dashboard /api/pty WebSocket handshake returned HTTP/1.1 101 Switching Protocols.
  • Manual local /api/sessions?limit=20&offset=0 check confirmed a repeatedly-compressed active session now appears in the first page.

Note

The TUI artifact freshness change overlaps with existing PRs called out in review (#20951, #20686, #21098). The systemd/reverse-proxy/session-list fixes are independent.

Remove systemd restart directives unsupported by older systemd releases. Trust local reverse proxy headers for the dashboard and allow proxied WebSocket requests when the upstream Host header matches the bound dashboard host. Update the TUI build freshness check to use the actual @hermes/ink build artifact so embedded chat startup does not rebuild on every PTY connection.
@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery comp/tui Terminal UI (ui-tui/ + tui_gateway/) comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists labels May 7, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Note: The TUI bundle staleness fix (point 3) in this PR overlaps with #20951, #20686, and #21098 which all fix _hermes_ink_bundle_stale() checking the wrong filename (ink-bundle.jsentry-exports.js).

@hejun041

hejun041 commented May 7, 2026

Copy link
Copy Markdown
Author

Added a follow-up commit fix: surface recently compressed dashboard sessions after checking for overlapping upstream/open-PR fixes. I found related resume/compression PRs (#15742, #13374) but not an existing fix for the dashboard /sessions list hiding repeated-compression tips or for /api/sessions ordering by last activity.

Additional validation:

  • venv/bin/python -m pytest tests/test_hermes_state.py::TestListSessionsRich tests/test_hermes_state.py::TestCompressionChainProjection tests/hermes_cli/test_web_server.py::TestPtyWebSocket tests/hermes_cli/test_web_server_host_header.py -q -o 'addopts=' → 42 passed
  • Local /api/sessions?limit=20&offset=0 now surfaces the active repeated-compression session on the first page.

@alt-glitch alt-glitch added comp/dashboard Web dashboard / control panel UI (dashboard/, landing) area/config Config system, migrations, profiles sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data and removed comp/cli CLI entry point, hermes_cli/, setup wizard comp/tui Terminal UI (ui-tui/ + tui_gateway/) labels Jun 27, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the focused compatibility and dashboard investigation. The systemd, TUI-staleness, and session-list portions have since landed through later changes, but the reverse-proxy portion needs a narrower rework.

Problems

  • hermes_cli/web_server.py:2920 changes only the peer-IP predicate. The WebSocket Host/Origin gate is separate and runs first on current main at hermes_cli/web_server.py:14335; it still rejects a public browser Origin for a loopback-bound dashboard. The linked #54072 discussion confirms this exact gap in this PR.
  • hermes_cli/web_server.py:4086 enables proxy_headers for every bind. Current main deliberately keeps it disabled for loopback binds and enables it only for authenticated public binds (hermes_cli/web_server.py:17030-17037) to preserve the loopback peer boundary.

Suggested changes

  • Salvage the remaining proxy case through the existing Host/Origin policy and configured trusted public URL, with end-to-end WebSocket coverage for both acceptance and rejection cases.
  • Keep the current conditional proxy_headers policy; omit portions already superseded by 4d18717b6 and c6ca11618.

Automated hermes-sweeper review.

Comment thread hermes_cli/web_server.py

bound_host = getattr(app.state, "bound_host", "")
host_header = ws.headers.get("host", "")
if bound_host and _is_accepted_host(host_header, bound_host):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes only the peer-IP check. A normal public reverse proxy still presents a browser Origin for its public hostname, while the separate Host/Origin guard compares that Origin to the loopback bound_host; this PR does not modify that path, so the reported origin_mismatch remains. Please route the configured trusted public URL through the Host/Origin policy rather than bypassing this peer check.

Comment thread hermes_cli/web_server.py
host=host,
port=port,
log_level="warning",
proxy_headers=True,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not enable proxy headers unconditionally for loopback binds. The current design keeps the loopback peer as the security boundary and enables forwarded-header handling only for authenticated public binds; preserve that distinction when salvaging the proxy case.

@teknium1 teknium1 added sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform area/sessions Session lifecycle, resume, persistence, history labels Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles area/sessions Session lifecycle, resume, persistence, history comp/dashboard Web dashboard / control panel UI (dashboard/, landing) comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants