fix(tests): live-system guard lets a gateway start inside a container exec
#106623 (ca16cafee4) blocks any test subprocess whose argv resolves to
`hermes gateway run|start|restart`, so the harness cannot spawn a runtime that
outlives the worker and restarts the developer's gateway. The matcher strips the
`-p <profile>` selector and reads through `sh -c`, so it also fired on
`docker exec -u hermes <ctr> sh -c 'hermes -p x gateway start'` and broke
tests/docker/test_profile_gateway.py on every Docker build since. A gateway
launched inside a container cannot reach the host unit or webhook port; the
guard now skips commands whose argv[0] is a container runtime (docker, podman,
nerdctl). Host-side spawns stay blocked.
This commit is contained in:
+16
-1
@@ -1428,6 +1428,14 @@ def _live_system_guard(request, monkeypatch):
|
||||
"daemon-reload", "try-restart", "reload-or-restart",
|
||||
)
|
||||
_PROCESS_KILLERS = ("pkill", "killall", "taskkill", "skill", "fuser")
|
||||
_CONTAINER_RUNTIMES = ("docker", "podman", "nerdctl")
|
||||
|
||||
def _first_token_basename(cmd_str: str) -> str:
|
||||
try:
|
||||
tokens = _shlex.split(cmd_str)
|
||||
except ValueError:
|
||||
tokens = cmd_str.split()
|
||||
return tokens[0].rsplit("/", 1)[-1].lower() if tokens else ""
|
||||
# Shell/launcher executables whose arguments are themselves commands —
|
||||
# argv[0]-only scanning must not exempt what they wrap.
|
||||
_WRAPPER_COMMANDS = (
|
||||
@@ -1563,7 +1571,14 @@ def _live_system_guard(request, monkeypatch):
|
||||
# sibling refactor moved the spawn seam and left tests patching the
|
||||
# facade. The canonical matcher, never an argv substring.
|
||||
from gateway.status import _gateway_command_subcommand
|
||||
if not lookalike_ok and _gateway_command_subcommand(cmd_str) in ("run", "start", "restart"):
|
||||
# A gateway launched INSIDE a container (`docker exec … hermes gateway start`) cannot
|
||||
# reach the host's systemd unit or webhook port; tests/docker/ exists to exercise it.
|
||||
in_container = _first_token_basename(cmd_str) in _CONTAINER_RUNTIMES
|
||||
if (
|
||||
not lookalike_ok
|
||||
and not in_container
|
||||
and _gateway_command_subcommand(cmd_str) in ("run", "start", "restart")
|
||||
):
|
||||
raise RuntimeError(
|
||||
f"tests/conftest.py live-system guard: blocked "
|
||||
f"subprocess.{name}({cmd!r}) — this would spawn a REAL "
|
||||
|
||||
@@ -37,3 +37,29 @@ def test_wrapped_killer_command_is_still_blocked():
|
||||
def test_env_wrapped_killer_command_is_still_blocked():
|
||||
with pytest.raises(RuntimeError, match="live-system guard"):
|
||||
subprocess.run(["env", "GUARD_TEST=1", "pkill", "-f", "hermes-guard-regression-nomatch"])
|
||||
|
||||
|
||||
def test_gateway_start_inside_a_container_exec_is_not_blocked():
|
||||
"""``docker exec <ctr> hermes gateway start`` launches the gateway INSIDE the container,
|
||||
where it cannot reach the host's systemd unit or webhook port; tests/docker/ depends on it.
|
||||
The binary is a stub so the argv stays exact without needing a Docker daemon."""
|
||||
import os
|
||||
import stat
|
||||
|
||||
stub_dir = os.path.join(os.environ["HERMES_HOME"], "stub-bin")
|
||||
os.makedirs(stub_dir, exist_ok=True)
|
||||
stub = os.path.join(stub_dir, "docker")
|
||||
with open(stub, "w") as fh:
|
||||
fh.write("#!/bin/sh\nexit 0\n")
|
||||
os.chmod(stub, os.stat(stub).st_mode | stat.S_IXUSR)
|
||||
result = subprocess.run(
|
||||
[stub, "exec", "-u", "hermes", "ctr", "sh", "-c", "hermes -p prof gateway start"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
assert result.returncode == 0
|
||||
|
||||
|
||||
def test_gateway_start_on_the_host_is_still_blocked():
|
||||
with pytest.raises(RuntimeError, match="REAL.*gateway runtime"):
|
||||
subprocess.run(["python", "-m", "hermes_cli.main", "gateway", "start"])
|
||||
|
||||
Reference in New Issue
Block a user