diff --git a/tests/tools/test_approval.py b/tests/tools/test_approval.py index 6f87e0728c3..8693b7e6a12 100644 --- a/tests/tools/test_approval.py +++ b/tests/tools/test_approval.py @@ -965,6 +965,41 @@ class TestGatewayProtection: assert dangerous is True assert "stop/restart" in desc + def test_hermes_gateway_stop_detected(self): + cmd = "hermes gateway stop" + dangerous, key, desc = detect_dangerous_command(cmd) + assert dangerous is True + assert "gateway" in desc.lower() + + def test_hermes_gateway_restart_with_profile_flag_detected(self): + """A profile flag between `hermes` and `gateway` must not slip past + the guard. See the 2026-04-11 ade-profile self-kill incident.""" + cmd = "hermes -p ade gateway restart" + dangerous, key, desc = detect_dangerous_command(cmd) + assert dangerous is True + assert "gateway" in desc.lower() + + def test_hermes_gateway_stop_with_long_profile_flag_detected(self): + cmd = "hermes --profile ade gateway stop" + dangerous, key, desc = detect_dangerous_command(cmd) + assert dangerous is True + + def test_hermes_gateway_multiple_flags_detected(self): + cmd = "hermes -p cocoa --verbose gateway restart" + dangerous, key, desc = detect_dangerous_command(cmd) + assert dangerous is True + + def test_hermes_gateway_status_with_profile_flag_not_flagged(self): + """Read-only subcommands stay allowed even with a profile flag.""" + cmd = "hermes -p ade gateway status" + dangerous, key, desc = detect_dangerous_command(cmd) + assert dangerous is False + + def test_hermes_gateway_start_not_flagged(self): + cmd = "hermes gateway start" + dangerous, key, desc = detect_dangerous_command(cmd) + assert dangerous is False + def test_pkill_hermes_detected(self): """pkill targeting hermes/gateway processes must be caught.""" cmd = 'pkill -f "cli.py --gateway"' diff --git a/tools/approval.py b/tools/approval.py index 3d7a8362af6..b7fab3b4318 100644 --- a/tools/approval.py +++ b/tools/approval.py @@ -424,8 +424,10 @@ DANGEROUS_PATTERNS = [ (r'\bfind\b.*-delete\b', "find -delete"), # Gateway lifecycle protection: prevent the agent from killing its own # gateway process. These commands trigger a gateway restart/stop that - # terminates all running agents mid-work. - (r'\bhermes\s+gateway\s+(stop|restart)\b', "stop/restart hermes gateway (kills running agents)"), + # terminates all running agents mid-work. Allow global flags between + # `hermes` and `gateway` (e.g. `hermes -p ade gateway restart`) so a + # profile flag can't slip the agent past the guard. + (r'\bhermes\s+(?:-{1,2}\S+(?:\s+\S+)?\s+)*gateway\s+(stop|restart)\b', "stop/restart hermes gateway (kills running agents)"), (r'\bhermes\s+update\b', "hermes update (restarts gateway, kills running agents)"), # Docker container lifecycle — any user with docker.sock mounted (a common # Docker Compose pattern) gives the agent the ability to restart/stop/kill