From a47f691df2074fe5795a640a28e37c9039a6c6bb Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Tue, 7 Jul 2026 17:58:10 +0000 Subject: [PATCH] fix(dashboard): honour HERMES_DASHBOARD_INSECURE env var for Docker/reverse-proxy deployments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: The June 2026 security hardening made should_require_auth() ignore the --insecure CLI flag on non-loopback binds. While the CLI flag was rightly deprecated as a security risk (it left unauthenticated public dashboards exposed to internet scanners), the HERMES_DASHBOARD_INSECURE env var was also silently broken — Docker/reverse-proxy deployments that explicitly set this env var to signal 'auth is handled upstream' still got the 'no auth providers registered' SystemExit. Fix: Restore honouring the HERMES_DASHBOARD_INSECURE env var in should_require_auth(). The env var is the deliberate operator opt-out — it's used by Docker/reverse-proxy deployments where authentication is provided by an upstream proxy, not by Hermes itself. The --insecure CLI flag remains a no-op (documented as such in the warning message, which now also tells users about the env var workaround). Changes: - hermes_cli/web_server.py: should_require_auth() checks HERMES_DASHBOARD_INSECURE env var and returns False when set - hermes_cli/web_server.py: start_server() warning now mentions HERMES_DASHBOARD_INSECURE as the supported bypass - hermes_cli/main.py: _maybe_setup_dashboard_auth_interactively() docstring updated to mention the env var as a no-op condition Fixes #59113 --- hermes_cli/main.py | 1 + hermes_cli/web_server.py | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/hermes_cli/main.py b/hermes_cli/main.py index e08246f16828..6ad2d2a20284 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -11771,6 +11771,7 @@ def _maybe_setup_dashboard_auth_interactively(args) -> None: No-ops (so the existing fail-closed ``SystemExit`` remains the backstop) when: * the bind is loopback (gate never engages), or + * ``HERMES_DASHBOARD_INSECURE=1`` (operator opted out), or * a provider is already registered, or * stdin/stdout isn't a TTY (Docker/s6, CI, piped ``--no-open`` runs). """ diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py index 8232ab77c0a5..65838a81eebb 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -391,6 +391,8 @@ def should_require_auth(host: str, allow_public: bool = False) -> bool: Truth table: host == loopback → False (no auth — local-only, trusted operator) host != loopback → True (gate engages — OAuth or password required) + HERMES_DASHBOARD_INSECURE=1 + → False (operator explicitly opts out) "Loopback" is 127.0.0.1, localhost, ::1. RFC1918 / CGNAT / link-local are deliberately treated as PUBLIC — a hostile device on the same LAN is exactly @@ -403,7 +405,14 @@ def should_require_auth(host: str, allow_public: bool = False) -> bool: unauthenticated-public-dashboard hole behind the June 2026 ``hermes-0day`` MCP-persistence campaign, where ``--insecure --host 0.0.0.0`` left the config/MCP/agent surface open to internet scanners. + + However, the ``HERMES_DASHBOARD_INSECURE`` env var is still honoured as a + deliberate operator opt-out — it is used by Docker/reverse-proxy deployments + where authentication is provided by an upstream proxy, not by Hermes itself. + The env var is the supported escape hatch; ``--insecure`` the CLI flag is not. """ + if os.environ.get("HERMES_DASHBOARD_INSECURE", "").strip() in ("1", "true", "yes"): + return False return host not in _LOOPBACK_HOST_VALUES @@ -15273,13 +15282,16 @@ def start_server( # the hermes-0day MCP-persistence campaign abused unauthenticated public # dashboards). If a caller still passes it, warn that it is now a no-op # rather than silently changing their expectation of an open bind. + # The ``HERMES_DASHBOARD_INSECURE`` env var is the supported escape hatch + # for Docker/reverse-proxy deployments (checked inside should_require_auth). if allow_public and host not in _LOOPBACK_HOST_VALUES: _log.warning( "--insecure no longer bypasses dashboard authentication. A " "non-loopback bind (%s) now ALWAYS requires an auth provider " "(OAuth or the bundled password provider). Configure one — see " "below — or bind to 127.0.0.1 and reach it over an SSH tunnel / " - "Tailscale.", host, + "Tailscale. To bypass, set HERMES_DASHBOARD_INSECURE=1.", + host, ) if app.state.auth_required: