Skip to content

feat(dashboard): HERMES_DASHBOARD_ALLOWED_HOSTS env for reverse-proxy / tunnel deployments#32362

Closed
olavon wants to merge 1 commit into
NousResearch:mainfrom
olavon:feat/dashboard-allowed-hosts-env
Closed

feat(dashboard): HERMES_DASHBOARD_ALLOWED_HOSTS env for reverse-proxy / tunnel deployments#32362
olavon wants to merge 1 commit into
NousResearch:mainfrom
olavon:feat/dashboard-allowed-hosts-env

Conversation

@olavon

@olavon olavon commented May 26, 2026

Copy link
Copy Markdown

Why

When running the Hermes dashboard behind a reverse proxy or tunnel (Cloudflare Tunnel, nginx, Traefik, Tailscale Funnel, etc.), the host-header DNS-rebinding defence (GHSA-ppp5-vxwm-4cf7) rejects requests because the public Host header (e.g. `hermes.example.com`) doesn't match the loopback bind.

Currently the only escape hatches are:

  • `--insecure --host 0.0.0.0` — much bigger blast radius, the docs explicitly warn against it on untrusted networks.
  • Patching the proxy to rewrite the Host header — not possible on all platforms (e.g. Cloudflare's token-managed tunnels no longer expose the HTTP Host Header field in the new Routes UI).

This is a fairly normal deployment pattern — bind to loopback, expose via an authenticated proxy/tunnel — and the defence shouldn't force users into less-safe alternatives.

What

New opt-in env var:

HERMES_DASHBOARD_ALLOWED_HOSTS=hermes.example.com,dashboard.internal
  • Comma-separated, case-insensitive, port-stripped.
  • Loopback names (`localhost`, `127.0.0.1`, `::1`) remain accepted unconditionally.
  • Any host not on the allowlist (and not matching the bound host / a loopback alias) is still rejected with HTTP 400 — the DNS-rebinding defence remains in place for everything else.
  • Empty / unset → no behaviour change (back-compat).

Pairs naturally with the existing `HERMES_DASHBOARD_HOST` (bind addr) env var.

How

`hermes_cli/web_server.py`:

  • New helper `_extra_allowed_hosts()` reads + parses the env var on each request (so a unit-file change is picked up on restart, no caching surprises).
  • `_is_accepted_host()` extended: on a loopback bind, allowlisted hosts pass; on a non-loopback bind, allowlisted hosts pass alongside the exact bound-host match.

`tests/hermes_cli/test_web_server_host_header.py`:

  • New `test_allowlisted_host_accepted_on_loopback_bind` covers allowlist hit, case-insensitive match, and confirms non-allowlisted hosts are still rejected.

Tested

```
$ ./venv/bin/python -m pytest tests/hermes_cli/test_web_server_host_header.py -q
......... [100%]
9 passed, 1 warning in 1.15s
```

Also verified end-to-end on my own deployment (Cloudflare Tunnel → `127.0.0.1:9119` with Cloudflare Access OTP gating the hostname):

  • `Host: hermes.olavo.org` → 200 ✅
  • `Host: evil.example.com` → 400 ✅

Docs

Happy to add a note to `website/docs/user-guide/docker.md` (where `HERMES_DASHBOARD_HOST` is documented) and/or a reverse-proxy section if you'd like — let me know your preference and I'll push it onto this branch.

…/tunnel deployments

Adds an opt-in allowlist for the dashboard's host-header check so it can sit
behind a reverse proxy or tunnel (Cloudflare Tunnel, nginx, Traefik, ...)
without disabling the DNS-rebinding defence (GHSA-ppp5-vxwm-4cf7) or
exposing the bind to a non-loopback interface.

  HERMES_DASHBOARD_ALLOWED_HOSTS=hermes.example.com,dashboard.internal

Comma-separated, case-insensitive, port-stripped. Loopback names remain
accepted unconditionally; any host not on the allowlist (and not matching
the bound host / a loopback alias) is still rejected with HTTP 400.

Tests: covers allowlisted accept, case-insensitivity, and non-allowlisted
reject on a loopback bind.
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/cli CLI entry point, hermes_cli/, setup wizard area/config Config system, migrations, profiles labels May 26, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Competing with #20136 (config-based, oldest), #25173, #27113 (env-var), #28578 (bundled), #28954 (CLI flag) for the same reverse-proxy host allowlist feature. This is the 6th open PR for this feature. Recommend consolidating.

@olavon

olavon commented May 26, 2026

Copy link
Copy Markdown
Author

Closing in favor of #27113 — same env-var approach, and per @alt-glitch's note above let's consolidate rather than pile on a 6th PR for this feature. Happy to review/test #27113 if helpful.

@olavon olavon closed this May 26, 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 comp/cli CLI entry point, hermes_cli/, setup wizard P3 Low — cosmetic, nice to have type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants