Remove prviliges drop when you never ran as root (#34837)

This commit is contained in:
Nacho Avecilla
2026-06-01 00:54:18 -03:00
committed by GitHub
parent 064875a540
commit 380ce4789b
5 changed files with 26 additions and 13 deletions

View File

@ -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

View File

@ -16,10 +16,11 @@
# first arg is an executable → exec it directly (sleep, bash, sh, …)
# first arg is anything else → exec `hermes <args>` (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 "$@"

View File

@ -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

View File

@ -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

View File

@ -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'
)