From 93dc5dee6fc2469e51fa1bbc8f5f5d7d51115160 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Mon, 23 Mar 2026 06:45:17 -0700 Subject: [PATCH] fix: prevent agents from starting gateway outside systemd management (#2617) An agent session killed the systemd-managed gateway (PID 1605) and restarted it with '&disown', taking it outside systemd's Restart= management. When the orphaned process later received SIGTERM, nothing restarted it. Add dangerous command patterns to detect: - 'gateway run' with & (background), disown, nohup, or setsid - These should use 'systemctl --user restart hermes-gateway' instead Also applied directly to main repo and fixed the systemd service: - Changed Restart=on-failure to Restart=always (clean SIGTERM = exit 0 = not a 'failure', so on-failure never triggered) - RestartSec=10 for reasonable restart delay --- tests/tools/test_approval.py | 37 ++++++++++++++++++++++++++++++++++++ tools/approval.py | 3 +++ 2 files changed, 40 insertions(+) diff --git a/tests/tools/test_approval.py b/tests/tools/test_approval.py index 4f61dd2ae..22ad88a8c 100644 --- a/tests/tools/test_approval.py +++ b/tests/tools/test_approval.py @@ -464,3 +464,40 @@ class TestForkBombDetection: dangerous, key, desc = detect_dangerous_command("echo hello:world") assert dangerous is False + +class TestGatewayProtection: + """Prevent agents from starting the gateway outside systemd management.""" + + def test_gateway_run_with_disown_detected(self): + cmd = "kill 1605 && cd ~/.hermes/hermes-agent && source venv/bin/activate && python -m hermes_cli.main gateway run --replace &disown; echo done" + dangerous, key, desc = detect_dangerous_command(cmd) + assert dangerous is True + assert "systemctl" in desc + + def test_gateway_run_with_ampersand_detected(self): + cmd = "python -m hermes_cli.main gateway run --replace &" + dangerous, key, desc = detect_dangerous_command(cmd) + assert dangerous is True + + def test_gateway_run_with_nohup_detected(self): + cmd = "nohup python -m hermes_cli.main gateway run --replace" + dangerous, key, desc = detect_dangerous_command(cmd) + assert dangerous is True + + def test_gateway_run_with_setsid_detected(self): + cmd = "hermes_cli.main gateway run --replace &disown" + dangerous, key, desc = detect_dangerous_command(cmd) + assert dangerous is True + + def test_gateway_run_foreground_not_flagged(self): + """Normal foreground gateway run (as in systemd ExecStart) is fine.""" + cmd = "python -m hermes_cli.main gateway run --replace" + dangerous, key, desc = detect_dangerous_command(cmd) + assert dangerous is False + + def test_systemctl_restart_not_flagged(self): + """Using systemctl to manage the gateway is the correct approach.""" + cmd = "systemctl --user restart hermes-gateway" + dangerous, key, desc = detect_dangerous_command(cmd) + assert dangerous is False + diff --git a/tools/approval.py b/tools/approval.py index 44029a8cc..233df07a2 100644 --- a/tools/approval.py +++ b/tools/approval.py @@ -49,6 +49,9 @@ DANGEROUS_PATTERNS = [ (r'\bxargs\s+.*\brm\b', "xargs with rm"), (r'\bfind\b.*-exec\s+(/\S*/)?rm\b', "find -exec rm"), (r'\bfind\b.*-delete\b', "find -delete"), + # Gateway protection: never start gateway outside systemd management + (r'gateway\s+run\b.*(&\s*$|&\s*;|\bdisown\b|\bsetsid\b)', "start gateway outside systemd (use 'systemctl --user restart hermes-gateway')"), + (r'\bnohup\b.*gateway\s+run\b', "start gateway outside systemd (use 'systemctl --user restart hermes-gateway')"), ]