mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-28 20:57:07 +08:00
Compare commits
1 Commits
fix/cli-wo
...
nanoclaw-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fdd44b6d1 |
1
cli.py
1
cli.py
@@ -609,6 +609,7 @@ def load_cli_config() -> Dict[str, Any]:
|
||||
"docker_volumes": "TERMINAL_DOCKER_VOLUMES",
|
||||
"docker_env": "TERMINAL_DOCKER_ENV",
|
||||
"docker_mount_cwd_to_workspace": "TERMINAL_DOCKER_MOUNT_CWD_TO_WORKSPACE",
|
||||
"docker_network": "TERMINAL_DOCKER_NETWORK",
|
||||
"docker_run_as_host_user": "TERMINAL_DOCKER_RUN_AS_HOST_USER",
|
||||
"docker_persist_across_processes": "TERMINAL_DOCKER_PERSIST_ACROSS_PROCESSES",
|
||||
"docker_orphan_reaper": "TERMINAL_DOCKER_ORPHAN_REAPER",
|
||||
|
||||
@@ -1145,6 +1145,7 @@ if _config_path.exists():
|
||||
"docker_volumes": "TERMINAL_DOCKER_VOLUMES",
|
||||
"docker_env": "TERMINAL_DOCKER_ENV",
|
||||
"docker_mount_cwd_to_workspace": "TERMINAL_DOCKER_MOUNT_CWD_TO_WORKSPACE",
|
||||
"docker_network": "TERMINAL_DOCKER_NETWORK",
|
||||
"docker_run_as_host_user": "TERMINAL_DOCKER_RUN_AS_HOST_USER",
|
||||
"docker_persist_across_processes": "TERMINAL_DOCKER_PERSIST_ACROSS_PROCESSES",
|
||||
"docker_orphan_reaper": "TERMINAL_DOCKER_ORPHAN_REAPER",
|
||||
|
||||
@@ -1001,6 +1001,9 @@ DEFAULT_CONFIG = {
|
||||
# Explicit opt-in: mount the host cwd into /workspace for Docker sessions.
|
||||
# Default off because passing host directories into a sandbox weakens isolation.
|
||||
"docker_mount_cwd_to_workspace": False,
|
||||
# Opt-in egress lockdown for Docker terminal sessions. When false,
|
||||
# Docker runs with --network=none so commands cannot reach the network.
|
||||
"docker_network": True,
|
||||
"docker_extra_args": [], # Extra flags passed verbatim to docker run
|
||||
# Explicit opt-in: run the Docker container as the host user's uid:gid
|
||||
# (via `--user`). When enabled, files written into bind-mounted dirs
|
||||
@@ -5325,6 +5328,7 @@ TERMINAL_CONFIG_ENV_MAP = {
|
||||
"docker_volumes": "TERMINAL_DOCKER_VOLUMES",
|
||||
"docker_env": "TERMINAL_DOCKER_ENV",
|
||||
"docker_mount_cwd_to_workspace": "TERMINAL_DOCKER_MOUNT_CWD_TO_WORKSPACE",
|
||||
"docker_network": "TERMINAL_DOCKER_NETWORK",
|
||||
"docker_extra_args": "TERMINAL_DOCKER_EXTRA_ARGS",
|
||||
"docker_run_as_host_user": "TERMINAL_DOCKER_RUN_AS_HOST_USER",
|
||||
"docker_persist_across_processes": "TERMINAL_DOCKER_PERSIST_ACROSS_PROCESSES",
|
||||
|
||||
84
tests/tools/test_docker_network_config.py
Normal file
84
tests/tools/test_docker_network_config.py
Normal file
@@ -0,0 +1,84 @@
|
||||
"""Regression tests for the Docker terminal network toggle.
|
||||
|
||||
Ported from NanoClaw PR #2713's opt-in egress lockdown idea. Hermes already
|
||||
has DockerEnvironment(network=False), but the terminal config path did not
|
||||
expose it, so operators could not request networkless Docker execution from
|
||||
config.yaml.
|
||||
"""
|
||||
|
||||
import tools.terminal_tool as terminal_tool
|
||||
from tools.environments import docker as docker_env
|
||||
|
||||
|
||||
def test_terminal_env_config_reads_docker_network_toggle(monkeypatch):
|
||||
monkeypatch.setenv("TERMINAL_DOCKER_NETWORK", "false")
|
||||
|
||||
config = terminal_tool._get_env_config()
|
||||
|
||||
assert config["docker_network"] is False
|
||||
|
||||
|
||||
def test_create_environment_passes_docker_network_toggle(monkeypatch):
|
||||
captured = {}
|
||||
sentinel = object()
|
||||
|
||||
def _fake_docker_environment(**kwargs):
|
||||
captured.update(kwargs)
|
||||
return sentinel
|
||||
|
||||
monkeypatch.setattr(terminal_tool, "_DockerEnvironment", _fake_docker_environment)
|
||||
|
||||
env = terminal_tool._create_environment(
|
||||
env_type="docker",
|
||||
image="python:3.11",
|
||||
cwd="/workspace",
|
||||
timeout=60,
|
||||
container_config={"docker_network": False},
|
||||
)
|
||||
|
||||
assert env is sentinel
|
||||
assert captured["network"] is False
|
||||
|
||||
|
||||
def test_docker_environment_adds_network_none_when_disabled(monkeypatch):
|
||||
commands = []
|
||||
|
||||
def fake_run(cmd, *args, **kwargs):
|
||||
commands.append(cmd)
|
||||
|
||||
class Result:
|
||||
returncode = 0
|
||||
stdout = "fake-container-id\n" if len(cmd) > 1 and cmd[1] == "run" else ""
|
||||
stderr = ""
|
||||
|
||||
return Result()
|
||||
|
||||
monkeypatch.setattr(docker_env, "find_docker", lambda: "/usr/bin/docker")
|
||||
monkeypatch.setattr(docker_env.subprocess, "run", fake_run)
|
||||
monkeypatch.setattr(docker_env.DockerEnvironment, "_storage_opt_supported", lambda self: False)
|
||||
|
||||
env = docker_env.DockerEnvironment(
|
||||
image="python:3.11",
|
||||
cwd="/workspace",
|
||||
timeout=60,
|
||||
task_id="network-none-test",
|
||||
network=False,
|
||||
)
|
||||
|
||||
run_cmd = next(cmd for cmd in commands if len(cmd) > 2 and cmd[1:3] == ["run", "-d"])
|
||||
assert "--network=none" in run_cmd
|
||||
env.cleanup()
|
||||
|
||||
|
||||
def test_docker_network_config_is_bridged_everywhere():
|
||||
from tests.tools.test_terminal_config_env_sync import (
|
||||
_cli_env_map_keys,
|
||||
_gateway_env_map_keys,
|
||||
_save_config_env_sync_keys,
|
||||
_terminal_tool_env_var_names,
|
||||
)
|
||||
|
||||
assert "docker_network" in _cli_env_map_keys()
|
||||
assert "docker_network" in _gateway_env_map_keys()
|
||||
assert "docker_network" in _save_config_env_sync_keys()
|
||||
assert "TERMINAL_DOCKER_NETWORK" in _terminal_tool_env_var_names()
|
||||
@@ -1173,6 +1173,7 @@ def _get_env_config() -> Dict[str, Any]:
|
||||
"docker_volumes": docker_volumes,
|
||||
"docker_env": docker_env,
|
||||
"docker_run_as_host_user": os.getenv("TERMINAL_DOCKER_RUN_AS_HOST_USER", "false").lower() in {"true", "1", "yes"},
|
||||
"docker_network": os.getenv("TERMINAL_DOCKER_NETWORK", "true").lower() in {"true", "1", "yes"},
|
||||
"docker_extra_args": docker_extra_args,
|
||||
# Cross-process container reuse (issue #20561). The docs claim
|
||||
# "ONE long-lived container shared across sessions" — this toggle
|
||||
@@ -1233,6 +1234,7 @@ def _create_environment(env_type: str, image: str, cwd: str, timeout: int,
|
||||
docker_forward_env = cc.get("docker_forward_env", [])
|
||||
docker_env = cc.get("docker_env", {})
|
||||
docker_extra_args = cc.get("docker_extra_args", [])
|
||||
docker_network = cc.get("docker_network", True)
|
||||
|
||||
if env_type == "local":
|
||||
return _LocalEnvironment(cwd=cwd, timeout=timeout)
|
||||
@@ -1255,6 +1257,7 @@ def _create_environment(env_type: str, image: str, cwd: str, timeout: int,
|
||||
forward_env=docker_forward_env,
|
||||
env=docker_env,
|
||||
run_as_host_user=cc.get("docker_run_as_host_user", False),
|
||||
network=docker_network,
|
||||
extra_args=docker_extra_args,
|
||||
persist_across_processes=cc.get("docker_persist_across_processes", True),
|
||||
)
|
||||
@@ -2011,6 +2014,7 @@ def terminal_tool(
|
||||
"docker_env": config.get("docker_env", {}),
|
||||
"docker_run_as_host_user": config.get("docker_run_as_host_user", False),
|
||||
"docker_extra_args": config.get("docker_extra_args", []),
|
||||
"docker_network": config.get("docker_network", True),
|
||||
"docker_persist_across_processes": config.get("docker_persist_across_processes", True),
|
||||
"docker_orphan_reaper": config.get("docker_orphan_reaper", True),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user