From 380ce4789bfc986f76863f85e1b422d840d7e178 Mon Sep 17 00:00:00 2001 From: Nacho Avecilla Date: Mon, 1 Jun 2026 00:54:18 -0300 Subject: [PATCH] Remove prviliges drop when you never ran as root (#34837) --- docker/cont-init.d/02-reconcile-profiles | 2 ++ docker/main-wrapper.sh | 11 ++++++----- docker/s6-rc.d/dashboard/run | 3 +++ docker/stage2-hook.sh | 11 +++++++---- hermes_cli/service_manager.py | 12 ++++++++---- 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/docker/cont-init.d/02-reconcile-profiles b/docker/cont-init.d/02-reconcile-profiles index 5050a24f1..ced666cff 100755 --- a/docker/cont-init.d/02-reconcile-profiles +++ b/docker/cont-init.d/02-reconcile-profiles @@ -42,4 +42,6 @@ if [ -d /run/service/.s6-svscan ]; then done fi +# Skip the drop when already non-root. +[ "$(id -u)" = 0 ] || exec /opt/hermes/.venv/bin/python -m hermes_cli.container_boot exec s6-setuidgid hermes /opt/hermes/.venv/bin/python -m hermes_cli.container_boot diff --git a/docker/main-wrapper.sh b/docker/main-wrapper.sh index a164b77ea..4952fad7c 100755 --- a/docker/main-wrapper.sh +++ b/docker/main-wrapper.sh @@ -16,10 +16,11 @@ # first arg is an executable → exec it directly (sleep, bash, sh, …) # first arg is anything else → exec `hermes ` (subcommand passthrough) # -# We drop to the hermes user via `s6-setuidgid` so the supervised -# workload runs unprivileged (UID 10000 by default). +# Drop to hermes via s6-setuidgid, but skip it when already non-root. set -e +drop() { [ "$(id -u)" = 0 ] && set -- s6-setuidgid hermes "$@"; exec "$@"; } + # HOME comes through with-contenv as /root (the /init context). Override # to the hermes user's home before dropping privileges so libraries that # resolve paths via $HOME (e.g. discord lockfile under XDG_STATE_HOME) @@ -31,13 +32,13 @@ cd /opt/data . /opt/hermes/.venv/bin/activate if [ $# -eq 0 ]; then - exec s6-setuidgid hermes hermes + drop hermes fi if command -v "$1" >/dev/null 2>&1; then # Bare executable — pass through directly. - exec s6-setuidgid hermes "$@" + drop "$@" fi # Hermes subcommand pass-through. -exec s6-setuidgid hermes hermes "$@" +drop hermes "$@" diff --git a/docker/s6-rc.d/dashboard/run b/docker/s6-rc.d/dashboard/run index d6cfa3f09..d6fd29caf 100755 --- a/docker/s6-rc.d/dashboard/run +++ b/docker/s6-rc.d/dashboard/run @@ -47,6 +47,9 @@ case "${HERMES_DASHBOARD_INSECURE:-}" in 1|true|TRUE|True|yes|YES|Yes) insecure="--insecure" ;; esac +# Skip the drop when already non-root. +# shellcheck disable=SC2086 # word-splitting of $insecure is intentional +[ "$(id -u)" = 0 ] || exec hermes dashboard --host "$dash_host" --port "$dash_port" --no-open $insecure # shellcheck disable=SC2086 # word-splitting of $insecure is intentional exec s6-setuidgid hermes hermes dashboard \ --host "$dash_host" --port "$dash_port" --no-open $insecure diff --git a/docker/stage2-hook.sh b/docker/stage2-hook.sh index 378316e5d..18668e7fc 100755 --- a/docker/stage2-hook.sh +++ b/docker/stage2-hook.sh @@ -20,6 +20,9 @@ set -eu HERMES_HOME="${HERMES_HOME:-/opt/data}" INSTALL_DIR="/opt/hermes" +# Drop to hermes via s6-setuidgid, but skip it when already non-root. +as_hermes() { [ "$(id -u)" = 0 ] || { "$@"; return; }; s6-setuidgid hermes "$@"; } + # --- Bootstrap HERMES_HOME as root --- # Create the directory (and any missing parents) while we still have root # privileges so the chown checks below see real metadata and the later @@ -199,7 +202,7 @@ fi # Use direct `mkdir -p` invocation (no `sh -c "..."` wrapper) so the # shell isn't a second interpreter — defends against $HERMES_HOME values # containing shell metacharacters. PR #30136 review item O2. -s6-setuidgid hermes mkdir -p \ +as_hermes mkdir -p \ "$HERMES_HOME/cron" \ "$HERMES_HOME/sessions" \ "$HERMES_HOME/logs" \ @@ -216,7 +219,7 @@ s6-setuidgid hermes mkdir -p \ # the hermes user so ownership matches the file's documented owner. # tee is invoked directly via s6-setuidgid (no `sh -c` wrapper) for the # same shell-metacharacter safety described above. -printf 'docker\n' | s6-setuidgid hermes tee "$HERMES_HOME/.install_method" >/dev/null \ +printf 'docker\n' | as_hermes tee "$HERMES_HOME/.install_method" >/dev/null \ || true # --- Seed config files (only on first boot) --- @@ -224,7 +227,7 @@ seed_one() { dest=$1 src=$2 if [ ! -f "$HERMES_HOME/$dest" ] && [ -f "$INSTALL_DIR/$src" ]; then - s6-setuidgid hermes cp "$INSTALL_DIR/$src" "$HERMES_HOME/$dest" + as_hermes cp "$INSTALL_DIR/$src" "$HERMES_HOME/$dest" fi } seed_one ".env" ".env.example" @@ -255,7 +258,7 @@ fi # the python binary's own bin-stub already sets up (sys.path is rooted # at the venv's site-packages by virtue of running .venv/bin/python). if [ -d "$INSTALL_DIR/skills" ]; then - s6-setuidgid hermes "$INSTALL_DIR/.venv/bin/python" "$INSTALL_DIR/tools/skills_sync.py" \ + as_hermes "$INSTALL_DIR/.venv/bin/python" "$INSTALL_DIR/tools/skills_sync.py" \ || echo "[stage2] Warning: skills_sync.py failed; continuing" fi diff --git a/hermes_cli/service_manager.py b/hermes_cli/service_manager.py index 1d0ce5d0d..731c37cd5 100644 --- a/hermes_cli/service_manager.py +++ b/hermes_cli/service_manager.py @@ -615,11 +615,13 @@ class S6ServiceManager: # guard. lines.append("export HERMES_S6_SUPERVISED_CHILD=1") if profile == "default": - lines.append("exec s6-setuidgid hermes hermes gateway run") + gateway_cmd = "hermes gateway run" else: - lines.append( - f"exec s6-setuidgid hermes hermes -p {shlex.quote(profile)} gateway run" - ) + gateway_cmd = f"hermes -p {shlex.quote(profile)} gateway run" + # Skip the drop when already non-root (setgroups() lacks CAP_SETGID → + # s6 boot-loop). + lines.append(f'[ "$(id -u)" = 0 ] || exec {gateway_cmd}') + lines.append(f"exec s6-setuidgid hermes {gateway_cmd}") return "\n".join(lines) + "\n" @staticmethod @@ -674,6 +676,8 @@ class S6ServiceManager: f'log_dir="$HERMES_HOME/logs/gateways/{prof}"\n' f'mkdir -p "$log_dir"\n' f'chown -R hermes:hermes "$log_dir" 2>/dev/null || true\n' + # Skip the drop when already non-root (CAP_SETGID). + f'[ "$(id -u)" = 0 ] || exec s6-log 1 n10 s1000000 T "$log_dir"\n' f'exec s6-setuidgid hermes s6-log 1 n10 s1000000 T "$log_dir"\n' )