Skip to content

fix(terminal): block raw-PID kills of the gateway's own process - #57305

Open
infocentr wants to merge 1 commit into
NousResearch:mainfrom
infocentr:fix/terminal-guard-raw-pid-kill
Open

infocentr wants to merge 1 commit into
NousResearch:mainfrom
infocentr:fix/terminal-guard-raw-pid-kill

Conversation

@infocentr

Copy link
Copy Markdown

Problem

The in-gateway terminal guard (_HERMES_GATEWAY=1) blocks gateway lifecycle commands by word pattern — systemctl restart hermes-gateway, hermes gateway restart, pkill ... hermes ... gateway. It does not catch kill -TERM <pid> with a raw numeric PID, because no hermes/gateway token appears in the command.

This is not hypothetical. Observed in production (2026-06-30): an agent doing maintenance inside the gateway was blocked four times by the pattern guard (including a systemd-run --on-active=3s delayed variant), then listed processes, found the gateway's PID, and ran kill -TERM <pid> — which sailed through. The gateway received an unplanned SIGTERM outside any supervisor stop transaction, exited nonzero, was marked Failed by systemd, and only came back via Restart=on-failure. The same session had already killed four sibling services by raw PID.

Fix

cron/lifecycle_guard.py gains command_kills_pid(text, pid): returns True when a kill/pkill invocation's argument list contains pid as a literal token. The terminal guard calls it with os.getpid() before the existing word-pattern check.

Because the check requires the literal current PID, it is exact where the word pattern is heuristic:

  • no false positives on prose (grep 'kill' notes.md), unrelated kills (kill -TERM 99999), or the PID embedded in longer numbers/filenames;
  • catches the observed shapes: multi-PID kills, sudo/path-prefixed kill, compound and multi-line commands.

Dynamic forms (kill $PID, xargs kill) are documented as out of scope — this is a foot-gun guard, not a security boundary, consistent with the module's existing design notes.

Tests

6 new tests (detector unit tests + terminal-tool integration: blocks own-PID kill inside gateway, passes unrelated kills through, inactive outside gateway). Full tests/tools/test_terminal_tool.py, tests/cron/, and tests/hermes_cli/test_gateway_restart_loop.py: 708 passed.

🤖 Generated with Claude Code

The in-gateway lifecycle guard pattern-matches command words (systemctl
restart hermes-gateway, pkill ... hermes ... gateway) but not
`kill -TERM <pid>` with a literal numeric PID — no hermes/gateway token
appears. Observed in production: an agent, blocked four times by the
word pattern, looked up the gateway's PID and killed it directly. The
gateway got an unplanned SIGTERM outside any supervisor stop job, exited
nonzero, and was marked failed before Restart= revived it.

At terminal-execution time the gateway knows its own PID, so the new
`command_kills_pid()` check in cron.lifecycle_guard is exact: it fires
only when a kill/pkill invocation's argument list contains the current
gateway PID as a literal token. No false positives on prose, kills of
unrelated processes, or PIDs embedded in longer numbers/filenames.
Dynamic forms (kill $PID) remain out of scope — this is a foot-gun
guard, not a security boundary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@alt-glitch alt-glitch added type/bug Something isn't working comp/cron Cron scheduler and job management tool/terminal Terminal execution and process management P2 Medium — degraded but workaround exists labels Jul 2, 2026
@teknium1

Copy link
Copy Markdown
Collaborator

Thanks for addressing a real gap: current main's in-gateway terminal guard only invokes the word-pattern matcher at tools/terminal_tool.py:2275-2277, so a literal raw-PID kill is not covered.

Problems

  • The added os.getpid() comparison in commit 3b0d9f4ed373b688284ad628baa206f2494ee0d6 is not backend-scoped. terminal_tool supports env_type="ssh" (tools/terminal_tool.py:1523-1533), and SSHEnvironment runs bash -c on the remote host (tools/environments/ssh.py:343-353). A remote process with the same numeric PID would be blocked even though it cannot signal the local gateway.
  • The added p?kill matcher also classifies pkill -P <gateway-pid> as a self-kill. -P is a parent-process selector, not a request to signal that PID.

Suggested changes

  • Scope the raw-PID detector to the local backend and add an SSH pass-through regression test.
  • Parse or exclude PID-valued pkill selectors; retain literal PID detection for kill operands.

Automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users labels Jul 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cron Cron scheduler and job management P2 Medium — degraded but workaround exists sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages tool/terminal Terminal execution and process management type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants