Skip to content

feat(dashboard): configurable hostname allowlist for tunnel/proxy setups - #47560

Open
bambalados wants to merge 1 commit into
NousResearch:mainfrom
bambalados:feat/dashboard-allowed-hosts
Open

feat(dashboard): configurable hostname allowlist for tunnel/proxy setups#47560
bambalados wants to merge 1 commit into
NousResearch:mainfrom
bambalados:feat/dashboard-allowed-hosts

Conversation

@bambalados

Copy link
Copy Markdown

Adds dashboard.allowed_external_hosts config key (default []) and
reads it in _is_accepted_host() so Cloudflare Tunnel and other
reverse-proxy setups can route WebSocket connections through loopback-
bound dashboards without triggering the Host header check.

Problem

When the dashboard binds to loopback (127.0.0.1 — the default), it
rejects any Host header that isn't localhost/::1. This breaks reverse-
proxy setups (nginx, caddy, Cloudflare Tunnel) where the proxy
forwards WS connections with the external hostname as the Host header.

Solution

A config key dashboard.allowed_external_hosts (list of hostnames,
case-insensitive, empty by default) that the Host header check also
accepts on loopback binds. Loopback names are always accepted
regardless.

Changes

  • hermes_cli/config.py: added allowed_external_hosts: [] default
  • hermes_cli/web_server.py: _get_allowed_external_hosts() + module-level memo, used in _is_accepted_host
  • tests/hermes_cli/test_dashboard_host_allowlist.py: 3 tests

Testing

pytest tests/hermes_cli/test_dashboard_host_allowlist.py -v -o 'addopts='
============================== 3 passed ==============================

Usage

dashboard:
  allowed_external_hosts:
    - hermes-gw.example.com

@alt-glitch alt-glitch added type/feature New feature or request comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have labels Jun 17, 2026
Adds dashboard.allowed_external_hosts config key (default []) and
reads it in _is_accepted_host() so Cloudflare Tunnel and other
reverse-proxy setups can route WS connections through loopback-
bound dashboards without triggering the Host header check.

The hostname set is read once at module load (restart to pick up
config changes).  Loopback names (localhost, 127.0.0.1, ::1) are
always accepted regardless of this setting.
@bambalados
bambalados force-pushed the feat/dashboard-allowed-hosts branch from 3e7db0f to 7287e2d Compare June 17, 2026 02:41
@alt-glitch alt-glitch added comp/dashboard Web dashboard / control panel UI (dashboard/, landing) and removed comp/gateway Gateway runner, session dispatch, delivery labels Jun 26, 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 config-based reverse-proxy approach. The underlying Host/Origin rejection remains present on current main: loopback binds only accept loopback values in hermes_cli/web_server.py:450-453, and the same helper gates HTTP plus WebSocket Host/Origin validation (hermes_cli/web_server.py:476, 14391, 14408).

Problems

  • The new allowlist would make a publicly reachable proxied hostname acceptable while loopback binds still bypass dashboard authentication (hermes_cli/web_server.py:393-412) and inject _SESSION_TOKEN into the SPA (hermes_cli/web_server.py:357-360). A Cloudflare Tunnel without upstream authentication would therefore expose the dashboard remotely. The configured external-host path needs an auth/trusted-proxy security boundary.
  • tests/hermes_cli/test_dashboard_host_allowlist.py:11-16 does not configure dashboard.allowed_external_hosts, so it never tests the new behavior or the WebSocket Origin path.

Suggested changes

  • Make external loopback aliases require dashboard authentication, or define and document an explicit authenticated trusted-proxy contract.
  • Add config-loading and WebSocket Host/Origin tests for an allowlisted host, alongside the existing reject-by-default case.

Automated hermes-sweeper review.

Comment thread hermes_cli/web_server.py
return True

# Loopback bind: accept the loopback names
# Loopback bind: accept the loopback names AND configured external hosts

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.

Allowlisting a remote hostname here still leaves should_require_auth() false for the loopback bind, so the SPA receives its loopback session token without dashboard authentication. Please require an authenticated trusted-proxy path before accepting externally reachable aliases.

def test_unknown_host_rejected():
assert _is_accepted_host("evil.example.com:443", "127.0.0.1") is False


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 test never configures dashboard.allowed_external_hosts, so the new allowlist path is untested. Add an isolated config fixture/reload that proves an allowlisted host is accepted and an unlisted one remains rejected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/dashboard Web dashboard / control panel UI (dashboard/, landing) P3 Low — cosmetic, nice to have sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users 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.

3 participants