From 4983c576b1ed118d56ddf98feed468f4b3a7042c Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Sun, 2 Aug 2026 21:49:58 +0530 Subject: [PATCH] fix(docker): gate the remaining every-boot chown walks (cron, pairing) Whole-bug-class follow-up to the profiles/ gate: cron/, platforms/ pairing, and legacy pairing/ ran chown_hermes_tree unconditionally on every boot with the identical warm-boot cost profile. Same tree_has_non_hermes_owner gate; find evaluates the top directory first and -quits on the first mismatch, so a mis-owned tree short-circuits in O(1) while a clean tree pays one read-only walk instead of a full chown -R inode rewrite. --- docker/stage2-hook.sh | 15 +++++++++------ tests/tools/test_stage2_hook_symlink_chown.py | 4 ++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/docker/stage2-hook.sh b/docker/stage2-hook.sh index df7a1c83b5..899c8e86ac 100755 --- a/docker/stage2-hook.sh +++ b/docker/stage2-hook.sh @@ -288,8 +288,10 @@ fi # Always reset ownership of $HERMES_HOME/cron on every boot for the same # docker-exec/root-write reason as profiles/. The cron scheduler state # (jobs.json) must stay readable by the unprivileged hermes runtime even -# after root-context maintenance commands or scheduler writes. -if [ -d "$HERMES_HOME/cron" ]; then +# after root-context maintenance commands or scheduler writes. Skip the +# recursive walk when the tree is already owned correctly (same warm-boot +# gate as profiles/). +if [ -d "$HERMES_HOME/cron" ] && tree_has_non_hermes_owner "$HERMES_HOME/cron"; then chown_hermes_tree "$HERMES_HOME/cron" fi @@ -315,13 +317,14 @@ fi # silently leaving the approved user unauthorized (#10270). The targeted # data-volume chown above only runs when the top-level $HERMES_HOME is # mis-owned, so warm boots skip it — this block makes a container restart -# self-heal. Tiny directory (a handful of small JSON files), so the cost -# is negligible. -if [ -d "$HERMES_HOME/platforms/pairing" ]; then +# self-heal. Tiny directory (a handful of small JSON files), so even the +# ownership pre-scan is negligible; gated for consistency with profiles/ +# and cron/. +if [ -d "$HERMES_HOME/platforms/pairing" ] && tree_has_non_hermes_owner "$HERMES_HOME/platforms/pairing"; then chown_hermes_tree "$HERMES_HOME/platforms/pairing" fi # Legacy location (pre-consolidated layout). -if [ -d "$HERMES_HOME/pairing" ]; then +if [ -d "$HERMES_HOME/pairing" ] && tree_has_non_hermes_owner "$HERMES_HOME/pairing"; then chown_hermes_tree "$HERMES_HOME/pairing" fi diff --git a/tests/tools/test_stage2_hook_symlink_chown.py b/tests/tools/test_stage2_hook_symlink_chown.py index 8c2a95233f..fd68d78def 100644 --- a/tests/tools/test_stage2_hook_symlink_chown.py +++ b/tests/tools/test_stage2_hook_symlink_chown.py @@ -122,3 +122,7 @@ def test_stage2_skips_recursive_repairs_when_tree_is_already_owned( assert "tree_has_non_hermes_owner() {" in stage2_text assert 'if [ -e "$HERMES_HOME/$sub" ] && tree_has_non_hermes_owner "$HERMES_HOME/$sub"; then' in stage2_text assert 'if [ -d "$HERMES_HOME/profiles" ] && tree_has_non_hermes_owner "$HERMES_HOME/profiles"; then' in stage2_text + # Sibling every-boot chown blocks carry the same warm-boot gate. + assert 'if [ -d "$HERMES_HOME/cron" ] && tree_has_non_hermes_owner "$HERMES_HOME/cron"; then' in stage2_text + assert 'if [ -d "$HERMES_HOME/platforms/pairing" ] && tree_has_non_hermes_owner "$HERMES_HOME/platforms/pairing"; then' in stage2_text + assert 'if [ -d "$HERMES_HOME/pairing" ] && tree_has_non_hermes_owner "$HERMES_HOME/pairing"; then' in stage2_text