diff --git a/hermes_cli/auth.py b/hermes_cli/auth.py index f4af3cc7dd..2e462772c5 100644 --- a/hermes_cli/auth.py +++ b/hermes_cli/auth.py @@ -1424,7 +1424,8 @@ def _save_auth_store(auth_store: Dict[str, Any], target_path: Optional[Path] = N auth_file.parent.mkdir(parents=True, exist_ok=True) # Tighten parent dir to 0o700 so siblings can't traverse to creds. # No-op on Windows (POSIX mode bits not enforced); ignore failures. - # secure_parent_dir refuses to chmod / or top-level dirs (#25821). + # secure_parent_dir refuses to chmod /, top-level dirs, or the + # hermes-agent install tree (#25821, #93050). secure_parent_dir(auth_file) auth_store["version"] = AUTH_STORE_VERSION auth_store["updated_at"] = datetime.now(timezone.utc).isoformat() @@ -2812,7 +2813,8 @@ def _read_qwen_cli_tokens() -> Dict[str, Any]: def _save_qwen_cli_tokens(tokens: Dict[str, Any]) -> Path: auth_path = _qwen_cli_auth_path() auth_path.parent.mkdir(parents=True, exist_ok=True) - # secure_parent_dir refuses to chmod / or top-level dirs (#25821). + # secure_parent_dir refuses to chmod /, top-level dirs, or the + # hermes-agent install tree (#25821, #93050). secure_parent_dir(auth_path) # Per-process random temp suffix avoids collisions between concurrent # writers and stale leftovers from a crashed prior write. @@ -5628,7 +5630,8 @@ def _write_shared_nous_state(state: Dict[str, Any]) -> None: with _nous_shared_store_lock(): path = _nous_shared_store_path() path.parent.mkdir(parents=True, exist_ok=True) - # secure_parent_dir refuses to chmod / or top-level dirs (#25821). + # secure_parent_dir refuses to chmod /, top-level dirs, or the + # hermes-agent install tree (#25821, #93050). secure_parent_dir(path) tmp = path.with_name(f"{path.name}.tmp.{os.getpid()}.{uuid.uuid4().hex}") # Create with 0o600 atomically via os.open(O_EXCL) — closes the TOCTOU diff --git a/hermes_constants.py b/hermes_constants.py index 6c45ba5825..34cc6f8e87 100644 --- a/hermes_constants.py +++ b/hermes_constants.py @@ -1022,7 +1022,13 @@ def secure_parent_dir(path: Path) -> None: prevent catastrophic host bricking when ``HERMES_HOME`` or other path env vars resolve to an unexpected location. - See https://github.com/NousResearch/hermes-agent/issues/25821. + Also refuses to chmod the hermes-agent install tree (the directory this + module lives in, and anything below it): restricting the install dir to + 0700 locks the runtime user out of traversing it when it does not own + the dir, as in the Docker image. A warning is logged when this happens. + + See https://github.com/NousResearch/hermes-agent/issues/25821 and + https://github.com/NousResearch/hermes-agent/pull/93050. """ parent = path.parent.resolve() # Refuse root and its direct children (/usr, /home, /var, /tmp, …). diff --git a/tools/mcp_oauth.py b/tools/mcp_oauth.py index 1eb6f57051..134b260a26 100644 --- a/tools/mcp_oauth.py +++ b/tools/mcp_oauth.py @@ -424,7 +424,8 @@ def _write_json(path: Path, data: dict) -> None: path.parent.mkdir(parents=True, exist_ok=True) # Tighten parent dir to 0o700 so siblings can't traverse to the creds. # No-op on Windows (POSIX mode bits aren't enforced); ignore failures. - # secure_parent_dir refuses to chmod / or top-level dirs (#25821). + # secure_parent_dir refuses to chmod /, top-level dirs, or the + # hermes-agent install tree (#25821, #93050). secure_parent_dir(path) # Per-process random suffix avoids collisions between concurrent # writers and stale leftovers from a prior crashed write.