diff --git a/agent/file_safety.py b/agent/file_safety.py index 7a70f96412507..0d71400c86eb7 100644 --- a/agent/file_safety.py +++ b/agent/file_safety.py @@ -77,15 +77,21 @@ def build_write_denied_prefixes(home: str) -> list[str]: ] -def get_safe_write_root() -> Optional[str]: - """Return the resolved HERMES_WRITE_SAFE_ROOT path, or None if unset.""" - root = os.getenv("HERMES_WRITE_SAFE_ROOT", "") - if not root: - return None - try: - return os.path.realpath(os.path.expanduser(root)) - except Exception: - return None +def get_safe_write_roots() -> set[str]: + """Return resolved HERMES_WRITE_SAFE_ROOT paths. Supports multiple directories + separated by ':' (Unix PATH-style). E.g., '/opt/data:/var/www/html'.""" + env = os.getenv("HERMES_WRITE_SAFE_ROOT", "") + if not env: + return set() + roots: set[str] = set() + for path in env.split(":"): + if path: + try: + resolved = os.path.realpath(os.path.expanduser(path)) + roots.add(resolved) + except Exception: + continue + return roots def is_write_denied(path: str) -> bool: @@ -124,9 +130,16 @@ def is_write_denied(path: str) -> bool: except Exception: pass - safe_root = get_safe_write_root() - if safe_root and not (resolved == safe_root or resolved.startswith(safe_root + os.sep)): - return True + safe_roots = get_safe_write_roots() + if safe_roots: + # Allow write if path is under ANY of the safe roots + allowed = False + for safe_root in safe_roots: + if resolved == safe_root or resolved.startswith(safe_root + os.sep): + allowed = True + break + if not allowed: + return True return False diff --git a/tools/memory_tool.py b/tools/memory_tool.py index eed5742ef3966..700aa44e7b920 100644 --- a/tools/memory_tool.py +++ b/tools/memory_tool.py @@ -633,7 +633,7 @@ def _read_file(path: Path) -> List[str]: return [] try: raw = path.read_text(encoding="utf-8") - except (OSError, IOError): + except (OSError, IOError, UnicodeDecodeError): return [] if not raw.strip(): @@ -673,7 +673,7 @@ def _detect_external_drift(self, target: str) -> Optional[str]: return None try: raw = path.read_text(encoding="utf-8") - except (OSError, IOError): + except (OSError, IOError, UnicodeDecodeError): return None if not raw.strip(): return None diff --git a/website/docs/reference/environment-variables.md b/website/docs/reference/environment-variables.md index 9e8220dd03781..84c05670a58a1 100644 --- a/website/docs/reference/environment-variables.md +++ b/website/docs/reference/environment-variables.md @@ -622,7 +622,7 @@ Advanced per-platform knobs for throttling the outbound message batcher. Most us | `HERMES_PREFILL_MESSAGES_FILE` | Path to a JSON file of ephemeral prefill messages injected at API-call time. | | `HERMES_ALLOW_PRIVATE_URLS` | `true`/`false` — allow tools to fetch localhost/private-network URLs. Off by default in gateway mode. | | `HERMES_REDACT_SECRETS` | `true`/`false` — control secret redaction in tool output, logs, and chat responses (default: `true`). | -| `HERMES_WRITE_SAFE_ROOT` | Optional directory prefix that restricts `write_file`/`patch` writes; paths outside require approval. | +| `HERMES_WRITE_SAFE_ROOT` | Optional directory prefix that restricts `write_file`/`patch` writes; paths outside require approval. Supports multiple directories separated by `:` (e.g., `/opt/data:/var/www/html`). | | `HERMES_DISABLE_LAZY_INSTALLS` | Internal bridge var set automatically in the official Docker image to prevent runtime dependency installs into the immutable `/opt/hermes` tree. The user-facing equivalent is `security.allow_lazy_installs: false` in `config.yaml`; do not set this in `.env`. | | `HERMES_DISABLE_FILE_STATE_GUARD` | Set to `1` to turn off the "file changed since you read it" guard on `patch`/`write_file`. | | `HERMES_CORE_TOOLS` | Comma-separated override for the canonical core tool list (advanced; rarely needed). | diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/reference/environment-variables.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/reference/environment-variables.md index 52ed671891bf9..ecaf1b2dc9b63 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/reference/environment-variables.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/reference/environment-variables.md @@ -560,7 +560,7 @@ Graph 事件(Teams 会议、日历、聊天等)的入站变更通知监听 | `HERMES_PREFILL_MESSAGES_FILE` | 包含在 API 调用时注入的临时预填消息的 JSON 文件路径。 | | `HERMES_ALLOW_PRIVATE_URLS` | `true`/`false`——允许工具获取 localhost/私有网络 URL。gateway 模式下默认关闭。 | | `HERMES_REDACT_SECRETS` | `true`/`false`——控制工具输出、日志和聊天响应中的密钥脱敏(默认:`true`)。 | -| `HERMES_WRITE_SAFE_ROOT` | 可选目录前缀,限制 `write_file`/`patch` 写入;超出范围的路径需要审批。 | +| `HERMES_WRITE_SAFE_ROOT` | 可选目录前缀,限制 `write_file`/`patch` 写入;超出范围的路径需要审批。支持多个目录,使用 `:` 分隔(例如:`/opt/data:/var/www/html`)。 | | `HERMES_DISABLE_FILE_STATE_GUARD` | 设为 `1` 可关闭 `patch`/`write_file` 上的"文件自上次读取后已更改"保护。 | | `HERMES_CORE_TOOLS` | 规范核心工具列表的逗号分隔覆盖(高级;极少需要)。 | | `HERMES_BUNDLED_SKILLS` | 启动时加载的内置技能列表的逗号分隔覆盖。 |