Skip to content

[Bug]: Docker-deployed Hermes can create root-owned config.yaml when CLI is run as root, causing gateway permission errors #16480

@lolikonloli

Description

@lolikonloli

Bug Description

In Docker deployments, Hermes normally starts correctly: the entrypoint runs as root, fixes ownership, and then drops privileges to the hermes user before running hermes gateway run.

The problem occurs when users enter the running container as root via docker exec and manually run hermes. This bypasses the entrypoint privilege-dropping logic, so the CLI runs as root and may create or modify $HERMES_HOME/config.yaml and other state files as root-owned.

For example, the following screenshot shows Hermes running as root even though HERMES_UID and HERMES_GID are set to 10000:

Image

This demonstrates that:

  • The CLI is executing as root
  • The environment variables (HERMES_UID, HERMES_GID) are present but not enforced
  • No privilege drop occurs in this execution path

Later, when the gateway runs normally as the hermes user, it can no longer access those files, causing permission errors.

The issue is not that the official gateway startup fails to drop privileges, but that interactive root execution is not guarded against and can silently poison the file ownership under $HERMES_HOME.

This may be related to previous permission-related reports such as #15865, where the reported symptom was:

chown: changing ownership of '/opt/data/config.yaml': Operation not permitted

PR #16096 fixed one specific entrypoint-side issue by moving the config.yaml chown/chmod handling into the root section before the gosu privilege drop, and by guarding the chown failure path.

However, the case described here is a different execution path: the user manually enters the running container as root and launches hermes, bypassing the entrypoint entirely. In that path, the fix from #16096 may repair config.yaml in some cases after gateway restart, but other files touched by the root-launched CLI or agent tools may still become root-owned.

This is also related in spirit to #4426 / #7357, where subprocess/tool environment isolation was improved by injecting a persistent HOME under $HERMES_HOME. That fix helps subprocesses write tool configuration into the persistent volume, but it does not prevent the main Hermes CLI process itself from being launched as root inside the container.

Therefore, the remaining issue is broader than config.yaml: any file under $HERMES_HOME created or modified by a root-launched Hermes CLI session may later become inaccessible to the normal gateway process running as the hermes user.


In later testing, restarting the gateway appeared to repair the ownership issue for config.yaml in some cases. However, permission errors may still occur for other files under $HERMES_HOME that were previously created or modified by the root-launched CLI or agent tools. For example, files related to history, memory, cache, logs, or state may also become root-owned and later fail to be read by the normal gateway process running as the hermes user.

Therefore, config.yaml is only one concrete example of the issue. The broader problem is that any file touched by a root-launched Hermes CLI session can become inaccessible to the normal non-root runtime.

Steps to Reproduce

  1. Build and start Hermes in a Docker dev environment:
    docker compose -p hermes-agent-dev up -d --build

  2. Enter the running container as root:
    docker exec -it hermes-agent-dev bash

  3. Launch the Hermes from this root shell:
    hermes setup

  4. Configure Hermes in the root-launched setup flow and trigger the config backup/update path.

  5. Verify file ownership:
    ls -l config.yaml
    The file is now owned by root.

  6. Launch hermes:
    hermes

  7. Observe permission-related errors when the gateway runs as the hermes user.
    chown: changing ownership of '/opt/data/config.yaml': Operation not permitted

Expected Behavior

When Hermes CLI is launched inside a Docker container, it should not execute any agent/tool command as root.

If the CLI is started from a root shell inside the container, Hermes should either:

  1. drop privileges to the hermes user before running any agent/tool command, or
  2. refuse to continue and instruct the user to run Hermes as the hermes user.

After restarting the gateway, it should continue to run normally as the hermes user without failing due to root-owned files under $HERMES_HOME.

Actual Behavior

After restarting the gateway, the service may fail with file permission / ownership errors because files under $HERMES_HOME have become root-owned.

config.yaml is one concrete example, but the same issue can also affect logs, cache files, state databases, or any other files created/modified by the root-launched CLI or agent tools.

Example error:

chown: changing ownership of '/opt/data/config.yaml': Operation not permitted

### Affected Component

Gateway (Telegram/Discord/Slack/WhatsApp), CLI (interactive chat), Configuration (config.yaml, .env, hermes setup)

### Messaging Platform (if gateway-related)

N/A (CLI only)

### Debug Report

```shell
--- hermes dump ---
version:          0.11.0 (2026.4.23) [(unknown)]
os:               Linux 6.6.87.2-microsoft-standard-WSL2 x86_64
python:           3.13.5
openai_sdk:       2.32.0
profile:          default
hermes_home:      /opt/data
model:            deepseek-v4-flash
provider:         deepseek
terminal:         local

api_keys:
  openrouter           not set
  openai               not set
  anthropic            not set
  anthropic_token      not set
  nous                 not set
  google/gemini        not set
  gemini               not set
  glm/zai              not set
  zai                  not set
  kimi                 not set
  minimax              not set
  deepseek             set
  dashscope            not set
  huggingface          not set
  nvidia               not set
  ai_gateway           not set
  opencode_zen         not set
  opencode_go          not set
  kilocode             not set
  firecrawl            not set
  tavily               not set
  browserbase          not set
  fal                  not set
  elevenlabs           not set
  github               not set

features:
  toolsets:           hermes-cli
  mcp_servers:        0
  memory_provider:    built-in
  gateway:            running (docker (foreground), pid 7)
  platforms:          none
  cron_jobs:          0
  skills:             81

config_overrides:
  display.streaming: True
--- end dump ---


--- agent.log (last 200 lines) ---
  in "/opt/data/config.yaml", line 400, column 1
2026-04-27 09:04:42,447 INFO run_agent: Loaded environment variables from /opt/data/.env
2026-04-27 09:04:59,887 INFO hermes_cli.plugins: Plugin 'openai' registered image_gen provider: openai
2026-04-27 09:04:59,888 INFO hermes_cli.plugins: Plugin 'openai-codex' registered image_gen provider: openai-codex
2026-04-27 09:04:59,919 INFO hermes_cli.plugins: Plugin 'xai' registered image_gen provider: xai
2026-04-27 09:04:59,956 INFO hermes_cli.plugins: Plugin discovery complete: 5 found, 4 enabled
2026-04-27 09:05:00,588 INFO run_agent: Loaded environment variables from /opt/data/.env
2026-04-27 09:05:01,337 INFO agent.auxiliary_client: Vision auto-detect: using main provider deepseek (deepseek-v4-flash)
2026-04-27 09:05:02,221 WARNING gateway.config: Failed to process config.yaml — falling back to .env / gateway.json values. Check /opt/data/config.yaml for syntax errors. Error: [Errno 13] Permission denied: '/opt/data/config.yaml'
2026-04-27 09:05:02,268 WARNING gateway.config: Failed to process config.yaml — falling back to .env / gateway.json values. Check /opt/data/config.yaml for syntax errors. Error: [Errno 13] Permission denied: '/opt/data/config.yaml'
2026-04-27 09:05:03,042 INFO agent.auxiliary_client: Vision auto-detect: using main provider deepseek (deepseek-v4-flash)
2026-04-27 09:05:03,274 INFO agent.auxiliary_client: Vision auto-detect: using main provider deepseek (deepseek-v4-flash)
2026-04-27 09:05:06,946 INFO agent.auxiliary_client: Vision auto-detect: using main provider deepseek (deepseek-v4-flash)
2026-04-27 09:05:08,270 INFO agent.auxiliary_client: Auxiliary auto-detect: using main provider deepseek (deepseek-v4-flash)
2026-04-27 09:05:10,413 INFO agent.auxiliary_client: Auxiliary auto-detect: using main provider deepseek (deepseek-v4-flash)
2026-04-27 09:05:10,413 INFO agent.auxiliary_client: Auxiliary title_generation: using auto (deepseek-v4-flash) at https://api.deepseek.com
2026-04-27 09:06:02,203 WARNING gateway.config: Failed to process config.yaml — falling back to .env / gateway.json values. Check /opt/data/config.yaml for syntax errors. Error: [Errno 13] Permission denied: '/opt/data/config.yaml'
2026-04-27 09:06:02,244 WARNING gateway.config: Failed to process config.yaml — falling back to .env / gateway.json values. Check /opt/data/config.yaml for syntax errors. Error: [Errno 13] Permission denied: '/opt/data/config.yaml'
2026-04-27 09:06:19,116 INFO gateway.run: Received SIGTERM/SIGINT — initiating shutdown
2026-04-27 09:06:19,124 WARNING gateway.run: Shutdown diagnostic — other hermes processes running:
  root           1  0.0  0.0   2564  1280 ?        Ss   08:04   0:00 /usr/bin/tini -g -- /opt/hermes/docker/entrypoint.sh gateway run
  hermes        29  0.0  0.0   4320  3520 pts/0    Ss+  08:04   0:00 bash
  hermes       324  0.0  0.0   6792  3680 ?        R    09:06   0:00 ps aux
2026-04-27 09:06:19,125 INFO gateway.run: Stopping gateway...
2026-04-27 09:06:19,128 INFO gateway.platforms.feishu: [Feishu] Disconnected
2026-04-27 09:06:19,128 INFO gateway.run: ✓ feishu disconnected
2026-04-27 09:06:19,130 INFO gateway.run: Gateway stopped
2026-04-27 09:06:19,130 INFO gateway.run: Cron ticker stopped
2026-04-27 09:06:19,131 INFO gateway.run: Exiting with code 1 (signal-initiated shutdown without restart request) so systemd Restart=on-failure can revive the gateway.
2026-04-27 09:06:21,479 INFO hermes_cli.plugins: Plugin 'openai' registered image_gen provider: openai
2026-04-27 09:06:21,479 INFO hermes_cli.plugins: Plugin 'openai-codex' registered image_gen provider: openai-codex
2026-04-27 09:06:21,540 INFO hermes_cli.plugins: Plugin 'xai' registered image_gen provider: xai
2026-04-27 09:06:21,602 INFO hermes_cli.plugins: Plugin discovery complete: 5 found, 4 enabled
2026-04-27 09:06:22,356 INFO gateway.run: Starting Hermes Gateway...
2026-04-27 09:06:22,356 INFO gateway.run: Session storage: /opt/data/sessions

Operating System

Ubuntu 24.04.1 LTS WSL

Python Version

3.13.5

Hermes Version

Hermes Agent v0.11.0 (2026.4.23), clone from main branch

Additional Logs / Traceback (optional)

Root Cause Analysis (optional)

The normal Docker startup path appears to be correct: the entrypoint starts as root, fixes ownership, and then runs the gateway as the hermes user.

The issue occurs when users enter the running container as root and manually launch hermes. This bypasses the entrypoint, so the CLI and any agent/tool commands run as root.

In hermes_cli/main.py, the CLI startup path loads environment/config state and initializes logging early. It also reads config.yaml for settings such as security.redact_secrets, but there is no guard that prevents root execution inside Docker or switches the process to the hermes user before agent/tool commands run.

The screenshot shows the CLI running as root while HERMES_UID=10000 and HERMES_GID=10000 are present. This suggests that these variables are not enforced in the manual CLI path; they only apply when the Docker entrypoint runs.

Therefore, files created or rewritten under $HERMES_HOME by the root-launched CLI can become root-owned. config.yaml is one concrete example, but logs, cache files, state databases, or other tool-modified files may be affected as well.

Proposed Fix (optional)

Add a Docker-root execution guard in hermes_cli/main.py.

When Hermes CLI starts inside a Docker container as root, it should detect this before running any agent/tool command. If detected, Hermes should either:

  1. drop privileges to the hermes user, or
  2. refuse to continue and instruct the user to run Hermes as the hermes user.

This would prevent root-launched CLI sessions from creating root-owned files under $HERMES_HOME, such as config.yaml, logs, cache files, or state databases.

Are you willing to submit a PR for this?

  • I'd like to fix this myself and submit a PR

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium — degraded but workaround existsarea/dockerDocker image, Compose, packagingcomp/cliCLI entry point, hermes_cli/, setup wizardtype/bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions