From c341a2d107182205d4092e66b0f428d6c35a6bcf Mon Sep 17 00:00:00 2001 From: Dusk <135010814+Dusk1e@users.noreply.github.com> Date: Thu, 28 May 2026 06:42:27 +0300 Subject: [PATCH] fix(docker): align HOME for dashboard and s6 gateway services (#33481) --- docker/s6-rc.d/dashboard/run | 4 ++++ hermes_cli/service_manager.py | 8 ++++++-- tests/hermes_cli/test_service_manager.py | 10 ++++++++++ tests/test_docker_home_override_scripts.py | 15 +++++++++++++++ 4 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 tests/test_docker_home_override_scripts.py diff --git a/docker/s6-rc.d/dashboard/run b/docker/s6-rc.d/dashboard/run index a48e8995dfc..31c75ad4189 100755 --- a/docker/s6-rc.d/dashboard/run +++ b/docker/s6-rc.d/dashboard/run @@ -19,6 +19,10 @@ case "${HERMES_DASHBOARD:-}" in ;; esac +# with-contenv repopulates HOME from /init as /root. Reset it before +# dropping privileges so HOME-anchored state lands under /opt/data. +export HOME=/opt/data + cd /opt/data # shellcheck disable=SC1091 . /opt/hermes/.venv/bin/activate diff --git a/hermes_cli/service_manager.py b/hermes_cli/service_manager.py index 1037760b69b..1d0ce5d0d72 100644 --- a/hermes_cli/service_manager.py +++ b/hermes_cli/service_manager.py @@ -566,8 +566,11 @@ class S6ServiceManager: 1. Sources HERMES_HOME (and any extra env) via with-contenv — so e.g. ``-e HERMES_HOME=/data/hermes`` is honored at run time, not Python-substituted at registration time (OQ8-C). - 2. Activates the bundled venv. - 3. Drops to the hermes user and exec's + 2. Resets ``HOME`` to ``/opt/data`` before the privilege drop + so with-contenv's root HOME does not leak into the + unprivileged gateway process. + 3. Activates the bundled venv. + 4. Drops to the hermes user and exec's ``hermes -p gateway run`` (or just ``hermes gateway run`` for the default profile — see below). @@ -597,6 +600,7 @@ class S6ServiceManager: "#!/command/with-contenv sh", "# shellcheck shell=sh", "set -e", + "export HOME=/opt/data", "cd /opt/data", ". /opt/hermes/.venv/bin/activate", ] diff --git a/tests/hermes_cli/test_service_manager.py b/tests/hermes_cli/test_service_manager.py index a5e2913bb4b..ca076f2959f 100644 --- a/tests/hermes_cli/test_service_manager.py +++ b/tests/hermes_cli/test_service_manager.py @@ -536,6 +536,7 @@ def test_s6_register_creates_service_dir_and_triggers_scan( assert run_path.is_file() assert run_path.stat().st_mode & 0o111 # executable run_text = run_path.read_text() + assert "export HOME=/opt/data" in run_text assert "hermes -p coder gateway run" in run_text assert "s6-setuidgid hermes" in run_text # Sentinel marking this as the supervised-child invocation. Without @@ -586,6 +587,15 @@ def test_s6_register_extra_env_is_quoted(s6_scandir, fake_subprocess_run) -> Non assert "export QUOTED='a'\"'\"'b'" in run_text +def test_render_run_script_resets_home_before_exec() -> None: + from hermes_cli.service_manager import S6ServiceManager + + run_text = S6ServiceManager._render_run_script("coder", {}) + + assert "export HOME=/opt/data" in run_text + assert "exec s6-setuidgid hermes hermes -p coder gateway run" in run_text + + def test_s6_register_rejects_invalid_profile_name(s6_scandir) -> None: from hermes_cli.service_manager import S6ServiceManager mgr = S6ServiceManager(scandir=s6_scandir) diff --git a/tests/test_docker_home_override_scripts.py b/tests/test_docker_home_override_scripts.py new file mode 100644 index 00000000000..d51ae06e17a --- /dev/null +++ b/tests/test_docker_home_override_scripts.py @@ -0,0 +1,15 @@ +"""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