From 3834e8972488911e4160f2f36bfef923e5c319d5 Mon Sep 17 00:00:00 2001 From: bradmarshall987 Date: Sun, 23 Aug 2026 10:45:12 -0600 Subject: [PATCH] Fix self-inflicted lockout: secure_parent_dir() chmod's /opt/hermes to 0700 secure_parent_dir() is called before credential file writes to harden the parent dir. Its existing safety check refuses only paths with fewer than 3 path parts, but /opt/hermes is exactly 3 parts, so it passes and gets chmod'd to 0700. UID 10000 (hermes) cannot then traverse the install dir and every new exec fails with 'Permission denied' until manual chmod 0755 /opt/hermes. This change: - Adds an explicit refusal for /opt/hermes parents in secure_parent_dir() - Adds chmod 0755 /opt/hermes to the Dockerfile install step next to the existing bin chmod, so the dir starts traversable Reproducer: any auth write to a file directly under /opt/hermes (e.g. auth.json when HERMES_HOME resolves there). Observed in production 2026-07-06 and 2026-08-22. See #25821 for context. Fixes #25821 follow-up. --- Dockerfile | 2 +- hermes_constants.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 2de6192715..37070bd991 100644 --- a/Dockerfile +++ b/Dockerfile @@ -298,7 +298,7 @@ RUN uv pip install --no-cache-dir --no-deps -e "." USER root RUN mkdir -p /opt/hermes/bin && \ cp /opt/hermes/docker/hermes-exec-shim.sh /opt/hermes/bin/hermes && \ - chmod 0755 /opt/hermes/bin/hermes && \ + chmod 0755 /opt/hermes /opt/hermes/bin/hermes && \ printf 'docker\n' > /opt/hermes/.install_method # The ``.install_method`` stamp is baked next to the running code (the install # tree), NOT into $HERMES_HOME. $HERMES_HOME (/opt/data) is a shared data diff --git a/hermes_constants.py b/hermes_constants.py index e7af188397..9b6c08a156 100644 --- a/hermes_constants.py +++ b/hermes_constants.py @@ -1023,6 +1023,15 @@ def secure_parent_dir(path: Path) -> None: # Refuse root and its direct children (/usr, /home, /var, /tmp, …). if parent == Path("/") or len(parent.parts) < 3: return + # Refuse /opt/hermes. The install dir lives on the image layer; + # chmodding it to 0700 breaks hermes-user traversal and produces + # spurious "Permission denied" on every new exec until manual + # `chmod 0755 /opt/hermes`. Reproducer: any auth write to a file + # directly under /opt/hermes (e.g. /opt/hermes/auth.json when + # HERMES_HOME resolves there) triggers the 0700 chmod and locks + # out UID 10000. See issue #25821 follow-up. + if str(parent) == "/opt/hermes": + return try: os.chmod(parent, 0o700) except OSError: