Skip to content

feat(dashboard): add HERMES_DASHBOARD_EXTRA_HOSTS for reverse-proxy / tunnel WS Origin acceptance - #75019

Closed
gering wants to merge 1 commit into
NousResearch:mainfrom
gering:feat/dashboard-extra-hosts
Closed

feat(dashboard): add HERMES_DASHBOARD_EXTRA_HOSTS for reverse-proxy / tunnel WS Origin acceptance#75019
gering wants to merge 1 commit into
NousResearch:mainfrom
gering:feat/dashboard-extra-hosts

Conversation

@gering

@gering gering commented Jul 30, 2026

Copy link
Copy Markdown

Summary

Closes #70059. Related: #54072, #50365.

Behind a reverse proxy or tunnel (nginx, Tailscale Serve, Cloudflare Tunnel) fronting a loopback-bound dashboard, HTTP works because the proxy rewrites the Host header — but the browser-set WebSocket Origin header carries the public hostname and cannot be rewritten by any proxy (it's a browser security header). _is_accepted_host() then refuses every WS upgrade:

pty refused: origin_mismatch origin=https://<proxy-hostname> bound=127.0.0.1

breaking chat, the events feed and the PTY, while the SPA itself loads fine.

Change

Adds the operator opt-in proposed in #70059:

HERMES_DASHBOARD_EXTRA_HOSTS=atlas.example.com,hermes.tail1234.ts.net
  • Comma-separated hostnames, case-insensitive, port-suffix tolerated — accepted in addition to the bound host in _is_accepted_host() (covers both the HTTP Host middleware and the WS Host/Origin guard, which share this helper).
  • Unset (default): behaviour is unchanged — strict loopback/bound-host matching.
  • Unlisted origins and DNS-rebinding Hosts are still rejected; this does not touch auth (_ws_auth_ok) or the loopback peer gate.
  • Read per call, so tests and long-lived processes see changes without a restart.

Testing

  • 5 new unit tests in tests/hermes_cli/test_web_server_host_header.py (TestExtraAcceptedHosts): accept-on-loopback-bind (incl. port + case), unlisted-host rejection, unset-env strictness, comma-list with whitespace, empty-entry handling.
  • pytest tests/hermes_cli/test_web_server_host_header.py11 passed (6 pre-existing + 5 new).
  • Verified live on a production instance (Hermes v0.19.0, Cloudflare Tunnel + CF Access → 127.0.0.1:9119, env set via systemd drop-in): dashboard chat / events feed / PTY connect again; foreign origins still refused.

…S Origin acceptance

Behind a reverse proxy or tunnel (nginx, Tailscale Serve, Cloudflare
Tunnel) fronting a loopback-bound dashboard, HTTP works because the
proxy rewrites the Host header — but the browser-set WebSocket Origin
header carries the public hostname and cannot be rewritten by any
proxy. _is_accepted_host() then refuses every WS upgrade
(origin_mismatch), breaking chat, events feed and PTY.

Add an operator opt-in: HERMES_DASHBOARD_EXTRA_HOSTS, a comma-separated
list of explicitly trusted hostnames accepted in addition to the bound
host. Unset (the default) keeps current strict behaviour; unlisted
origins and DNS-rebinding Hosts are still rejected.

Closes NousResearch#70059
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/dashboard Web dashboard / control panel UI (dashboard/, landing) area/config Config system, migrations, profiles area/auth Authentication, OAuth, credential pools sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data needs-decision Awaiting maintainer decision before any implementation labels Jul 30, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related to #70059 and the existing reverse-proxy Origin implementations. This current-main rebase uses a separate multi-host environment allowlist; it is not a duplicate of the public-URL, config, or forwarded-host approaches. Maintainers need to choose the intended trust/config contract.

@teknium1

Copy link
Copy Markdown
Contributor

Thanks for reproducing a real reverse-proxy WebSocket failure and for adding focused strictness coverage. This is an automated hermes-sweeper review.

  • Current main rejects a public proxy Origin on a loopback-bound dashboard through hermes_cli/web_server.py:14371; HTTP Host validation shares the same helper at hermes_cli/web_server.py:525.
  • The proposed mechanism is a new user-facing non-secret HERMES_DASHBOARD_EXTRA_HOSTS setting (54278ff9b4f32b1439f1bce4a15fb2836af302f9). Repository policy requires behavioral configuration to live in config.yaml, not in new HERMES_* environment variables (AGENTS.md:102-106).
  • Please re-scope a follow-up around the established dashboard configuration surface, with exact-match validation and positive regression coverage for both the HTTP middleware and WebSocket Host/Origin paths. The related feat(dashboard): HERMES_DASHBOARD_EXTRA_HOSTS for reverse-proxy WS Origin #70064 review identified the same configuration-direction requirement.

Closing under the standing configuration policy rather than on the merits of the reproduction.


Closed as not-planned per standing maintainer policy (env-var-for-config). This is a design-direction decision, not a code-quality judgment — see the Contribution Rubric in AGENTS.md for what the project is looking for. If you believe this policy was misapplied to your change, comment here and a maintainer will take a look.

@teknium1 teknium1 closed this Jul 30, 2026
@teknium1 teknium1 added the sweeper:not-planned Sweeper: closed per standing maintainer policy (design direction) label Jul 30, 2026
gering added a commit to gering/hermes-agent that referenced this pull request Aug 17, 2026
…n acceptance

Behind a reverse proxy or tunnel (nginx, Tailscale Serve, Cloudflare
Tunnel) fronting a loopback-bound dashboard, HTTP works because the
proxy rewrites the Host header — but the browser-set WebSocket Origin
header carries the public hostname and cannot be rewritten by any
proxy. _is_accepted_host() then refuses every WS upgrade
(origin_mismatch), breaking chat, events feed and PTY.

Add an operator opt-in on the established dashboard config surface:
dashboard.extra_hosts in config.yaml, a list of explicitly trusted
hostnames accepted in addition to the bound host.

- Exact-match only: bare hostnames, compared case-insensitively with
  the port stripped; wildcards/schemes/paths are ignored with a single
  warning (fail closed, never partially honoured).
- Read per request via the mtime-cached load_config_readonly() (no
  disk I/O on the hot path), so a running dashboard picks up config
  edits without a restart.
- Unset (the default) keeps current strict behaviour; unlisted hosts
  and DNS-rebinding Hosts are still rejected.
- Positive + negative regression coverage through BOTH real request
  paths: the HTTP host_header_middleware and the WebSocket Host/Origin
  guard, plus unit coverage of the validation rules.

Re-scope of NousResearch#75019 (closed under the env-var-for-config policy):
behavioural settings belong in config.yaml, not HERMES_* env vars.

Closes NousResearch#70059

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QwuEkr3qrADt5ctc5mXZmn
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/auth Authentication, OAuth, credential pools area/config Config system, migrations, profiles comp/dashboard Web dashboard / control panel UI (dashboard/, landing) needs-decision Awaiting maintainer decision before any implementation P3 Low — cosmetic, nice to have sweeper:not-planned Sweeper: closed per standing maintainer policy (design direction) sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dashboard: add HERMES_DASHBOARD_EXTRA_HOSTS for reverse-proxy / Tailscale WS Origin acceptance

3 participants