mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-29 06:31:32 +00:00
The s6 dashboard run script flipped `--insecure` on whenever
`HERMES_DASHBOARD_HOST` was anything other than 127.0.0.1 / localhost.
That comment ("the dashboard refuses otherwise") predates the OAuth
auth gate: back when it was written, `start_server` would SystemExit
on any non-loopback bind, so the run script's `--insecure` was the
only way to make in-container deployments work at all.
The gate has since been replaced by `should_require_auth(host,
allow_public)`, which engages the OAuth flow when a
`DashboardAuthProvider` is registered (the bundled `dashboard_auth/nous`
provider auto-registers on `HERMES_DASHBOARD_OAUTH_CLIENT_ID`) and
fails closed with a specific operator-facing error when none is. The
host-derived `--insecure` ran upstream of all that and silently
disabled the gate on every container-deployed dashboard.
Most visible under the portal's wildcard-subdomain rollout: every Fly
machine binds 0.0.0.0 so the edge can reach Flycast, every machine
boots with the correct `HERMES_DASHBOARD_OAUTH_CLIENT_ID`, the nous
provider registers — and `/api/status` still returns
`{"auth_required": false, "auth_providers": ["nous"]}` because the
run script disabled the gate before `start_server` ever saw the
request. The dashboard SPA was served to anyone, no `/login` redirect,
no OAuth challenge.
Fix: derive `--insecure` from an explicit opt-in env var,
`HERMES_DASHBOARD_INSECURE` (truthy values matching the rest of the
s6 boolean envs: 1, true, TRUE, True, yes, YES, Yes). Operators on
trusted LANs behind a reverse proxy without the OAuth contract
(the existing `docker-compose.windows.yml` use case) opt in
explicitly; portal-managed agent deployments leave it unset and let
the gate engage.
`docker-compose.windows.yml` already passes `--insecure` on the
`command:` array directly (line 38), so it doesn't depend on the s6
auto-injection. No compose-file change required.
Tests:
* `tests/test_docker_home_override_scripts.py` — extends the existing
static-text guard with a regression assertion that the legacy
host-derived case-statement is gone and the new env-var opt-in is
present (locks against accidental revert).
* `tests/docker/test_dashboard.py` — adds two Docker-in-Docker tests
exercising the actual `/api/status` round-trip:
- 0.0.0.0 bind + `HERMES_DASHBOARD_OAUTH_CLIENT_ID` → gate engaged
- 0.0.0.0 bind + `HERMES_DASHBOARD_INSECURE=1` → gate disabled
Docs:
* `website/docs/user-guide/docker.md` + zh-Hans i18n — adds the new
env var to the table, replaces the stale prose ("the entrypoint
no longer auto-enables insecure mode" — which until this PR was
flat-out wrong) with an accurate description of the gate's
trigger conditions and the explicit opt-out.
shellcheck clean. Python static-text test passes locally. Behavioural
test will run against any future image build (CI's Docker harness).
48 lines
1.9 KiB
Python
48 lines
1.9 KiB
Python
"""Regression tests for Docker HOME overrides under s6/with-contenv."""
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
DASHBOARD_RUN = REPO_ROOT / "docker" / "s6-rc.d" / "dashboard" / "run"
|
|
|
|
|
|
def test_dashboard_run_resets_home_before_dropping_privileges() -> None:
|
|
text = DASHBOARD_RUN.read_text(encoding="utf-8")
|
|
|
|
assert "#!/command/with-contenv sh" in text
|
|
assert "export HOME=/opt/data" in text
|
|
assert "exec s6-setuidgid hermes hermes dashboard" in text
|
|
|
|
|
|
def test_dashboard_run_does_not_derive_insecure_from_bind_host() -> None:
|
|
"""The s6 dashboard run script MUST NOT auto-add ``--insecure`` based on
|
|
``HERMES_DASHBOARD_HOST``. Doing so disables the OAuth auth gate on
|
|
every non-loopback bind even when an auth provider is registered —
|
|
the exact regression that exposed every wildcard-subdomain agent
|
|
dashboard publicly until early 2026.
|
|
|
|
The opt-in is now explicit: ``HERMES_DASHBOARD_INSECURE=1`` (truthy).
|
|
The auth gate is the authority on whether non-loopback binds are safe.
|
|
"""
|
|
text = DASHBOARD_RUN.read_text(encoding="utf-8")
|
|
|
|
# No legacy host-derived flip.
|
|
assert '127.0.0.1|localhost' not in text, (
|
|
"Run script still derives --insecure from the bind host. The gate "
|
|
"is the authority now — opt in via HERMES_DASHBOARD_INSECURE instead."
|
|
)
|
|
assert 'case "$dash_host" in' not in text, (
|
|
"Legacy host-derived --insecure case-statement is back."
|
|
)
|
|
|
|
# New opt-in env var present.
|
|
assert "HERMES_DASHBOARD_INSECURE" in text, (
|
|
"Explicit HERMES_DASHBOARD_INSECURE opt-in is missing."
|
|
)
|
|
# Truthy values aligned with the rest of the s6 scripts
|
|
# (HERMES_DASHBOARD, HERMES_DASHBOARD_TUI).
|
|
for truthy in ("1", "true", "TRUE", "True", "yes", "YES", "Yes"):
|
|
assert truthy in text, (
|
|
f"HERMES_DASHBOARD_INSECURE should accept truthy value {truthy!r}"
|
|
)
|