feat(dashboard): allow trusted reverse-proxy Host headers#27113
feat(dashboard): allow trusted reverse-proxy Host headers#27113jadametz wants to merge 1 commit into
Conversation
Add HERMES_DASHBOARD_ALLOWED_HOSTS so localhost-bound dashboards can accept explicit Host headers from trusted local reverse proxies such as Tailscale Serve without falling back to all-interface insecure binds. Keep the default DNS-rebinding protection intact and cover the opt-in behavior with host-header tests.
|
Hey @jadametz — there are a handful of competing dashboard-host-allowlist PRs open right now and the duplicate-detector bot linked them; sharing one observation that may be useful. Same placement comment as I left on #25173: the Two small things in your PR that I really liked and would borrow:
I opened #29195 with the same env-var name idea but gated inside the loopback-bind branch + a regression test for the non-loopback-bind case. Happy to fold your |
|
Thanks for the focused patch and the useful tests around Host normalization. Problems
Suggested changes
Automated hermes-sweeper review; maintainers make the final call. |
What does this PR do?
Adds an explicit trusted Host-header allowlist for the Hermes dashboard so operators can keep the dashboard bound to loopback while accessing it through a trusted local reverse proxy such as Tailscale Serve.
Today, that safer setup can fail with
Invalid Host headerbecause the browser reaches the dashboard using the reverse proxy's external hostname while Hermes is bound to127.0.0.1. Binding the dashboard directly to0.0.0.0 --insecureworks around that, but it weakens the default safety model.This PR keeps the existing DNS-rebinding protection by default and adds an opt-in
HERMES_DASHBOARD_ALLOWED_HOSTSenvironment variable for trusted reverse-proxy hostnames. Host values are normalized before comparison, includinghost:portand bracketed IPv6 forms such as[::1]:9119.It also documents the Tailscale Serve/reverse-proxy pattern and clarifies why these trusted proxy hosts are not added to CORS origins: navigating directly to the proxied dashboard hostname is not a cross-origin browser fetch.
Related Issue
Related to #10567 and #15731.
Also related to the currently open #20136, which takes a broader config-based approach for trusted reverse proxy hosts and WebSocket validation. This PR is the smaller environment-variable scoped approach for HTTP Host-header validation and documentation.
Type of Change
Changes Made
hermes_cli/web_server.py_host_header_name()to normalize Host headers consistently._configured_allowed_host_values()to parseHERMES_DASHBOARD_ALLOWED_HOSTSas comma- or semicolon-delimited hostnames._is_accepted_host()while preserving default localhost/DNS-rebinding protections.tests/hermes_cli/test_web_server_host_header.pywebsite/docs/user-guide/features/web-dashboard.mdHERMES_DASHBOARD_ALLOWED_HOSTS.How to Test
Run the Host-header test suite:
python -m pytest tests/hermes_cli/test_web_server_host_header.py -q -o 'addopts='Expected result:
For a manual Tailscale Serve/local reverse-proxy setup, keep Hermes bound to loopback and set the trusted proxy hostname:
Verify accepted and rejected Host headers against the loopback-bound dashboard:
The configured trusted host should be accepted, while an unlisted external host should still be rejected.
Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests passDocumentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/A (HERMES_DASHBOARD_ALLOWED_HOSTSis an environment variable, not a config key)CONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/AScreenshots / Logs
Targeted tests pass:
I also attempted the full suite with:
python -m pytest tests/ -q -o 'addopts='The run does not complete cleanly on the current branch because of an existing baseline failure that is also present on
origin/main:Both
origin/mainand this branch currently have:So the full-suite checkbox is intentionally left unchecked; the targeted dashboard tests for this change pass.