Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions tests/tools/test_approval.py
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,61 @@ def test_safe_kill_pid_not_flagged(self):
dangerous, _, _ = detect_dangerous_command(cmd)
assert dangerous is False

def test_kill_dollar_pidof_detected(self):
"""`kill $(pidof hermes)` is the BSD/Linux equivalent of the
pgrep expansion and bypasses the pkill/killall name pattern
in the same way. See issue #33071."""
cmd = "kill -TERM $(pidof hermes_cli.main)"
dangerous, _, desc = detect_dangerous_command(cmd)
assert dangerous is True
assert "pidof" in desc.lower() or "pgrep" in desc.lower()

Comment on lines +883 to +886
def test_kill_backtick_pidof_detected(self):
cmd = "kill -9 `pidof hermes`"
dangerous, _, _ = detect_dangerous_command(cmd)
assert dangerous is True


class TestLaunchctlGatewayLifecycle:
"""launchctl stop/kickstart/bootout/unload against the Hermes service
label achieves the same effect as `hermes gateway stop|restart` and
must require the same approval. See issue #33071.
"""

def test_launchctl_stop_hermes_detected(self):
cmd = "launchctl stop ai.hermes.gateway"
dangerous, _, desc = detect_dangerous_command(cmd)
assert dangerous is True
assert "launchd" in desc.lower() or "hermes" in desc.lower()

def test_launchctl_kickstart_hermes_detected(self):
cmd = "launchctl kickstart -k system/ai.hermes.gateway"
dangerous, _, _ = detect_dangerous_command(cmd)
assert dangerous is True

def test_launchctl_bootout_hermes_detected(self):
cmd = "launchctl bootout system/ai.hermes.gateway"
dangerous, _, _ = detect_dangerous_command(cmd)
assert dangerous is True

def test_launchctl_unload_hermes_detected(self):
cmd = "launchctl unload ~/Library/LaunchAgents/ai.hermes.gateway.plist"
dangerous, _, _ = detect_dangerous_command(cmd)
assert dangerous is True

def test_launchctl_print_unrelated_not_flagged(self):
"""Read-only inspection of an unrelated launchd label must stay safe."""
cmd = "launchctl print system/com.apple.WindowServer"
dangerous, _, _ = detect_dangerous_command(cmd)
assert dangerous is False

def test_launchctl_stop_unrelated_not_flagged(self):
"""`launchctl stop` on a non-Hermes label is out of scope for the
gateway-lifecycle guard."""
cmd = "launchctl stop com.example.unrelated"
dangerous, _, _ = detect_dangerous_command(cmd)
assert dangerous is False


class TestGitDestructiveOps:
"""git reset --hard, push --force, clean -f, branch -D can destroy
Expand Down
11 changes: 9 additions & 2 deletions tools/approval.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,8 +398,15 @@ def _sudo_stdin_block_result(description: str) -> dict:
# The name-based pattern above catches `pkill hermes` but not
# `kill -9 $(pgrep -f hermes)` because the substitution is opaque
# to regex at detection time. Catch the structural pattern instead.
(r'\bkill\b.*\$\(\s*pgrep\b', "kill process via pgrep expansion (self-termination)"),
(r'\bkill\b.*`\s*pgrep\b', "kill process via backtick pgrep expansion (self-termination)"),
# `pidof` is the BSD/Linux alternative to `pgrep` and is equally
# opaque, so include it in the same alternation.
(r'\bkill\b.*\$\(\s*(pgrep|pidof)\b', "kill process via pgrep/pidof expansion (self-termination)"),
(r'\bkill\b.*`\s*(pgrep|pidof)\b', "kill process via backtick pgrep/pidof expansion (self-termination)"),
# launchctl-driven gateway stop/restart on macOS. The agent can bypass
# the `hermes gateway stop|restart` pattern above by driving launchd
# directly against the service label (commonly `ai.hermes.gateway`).
# Catch the operations that stop, restart, or unload it.
(r'\blaunchctl\s+(stop|kickstart|bootout|unload|kill|disable|remove)\b.*\b(hermes|ai\.hermes)\b', "stop/restart hermes launchd service (kills running agents)"),
Comment on lines +408 to +409
# File copy/move/edit into sensitive system paths (/etc/ and macOS
# /private/etc/ mirror).
(rf'\b(cp|mv|install)\b.*\s{_SYSTEM_CONFIG_PATH}', "copy/move file into system config path"),
Expand Down
Loading