Skip to content

fix(guard): block launchctl submit/bootstrap and wrapped-script gateway restarts - #75972

Merged
teknium1 merged 4 commits into
mainfrom
fix/launchctl-submit-guard
Aug 1, 2026
Merged

fix(guard): block launchctl submit/bootstrap and wrapped-script gateway restarts#75972
teknium1 merged 4 commits into
mainfrom
fix/launchctl-submit-guard

Conversation

@teknium1

@teknium1 teknium1 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

PR body draft — fix/launchctl-submit-guard

Title

fix(gateway): block launchctl submit/bootstrap lifecycle-guard bypass (restart loops)

Body

Fixes #62891

A macOS gateway session that was correctly blocked from restarting its own gateway laundered the restart through launchctl submit, creating a persistent launchd keepalive job that killed/respawned the gateway 9,447 times (plus two later production reproductions: a neutral-label ai.hermes.svc-reload-tmp variant and a 263-run submit of an existing helper script).

What this does

Fixes the class, not just the instance — the guard regex is shared between the cron creation-time guard (cron/lifecycle_guard.py) and the terminal execution-time hard-block (tools/terminal_tool.py), and both sites get all three layers:

  1. Branch B regex extensionsubmit and bootstrap added to the launchctl verb list, plus POSIX shell line-continuation normalization so the exact multi-line reported command can't slip past [^\n]*. (Drawn from fix(gateway): block launchctl submit in the gateway lifecycle guard #62896 by @jackjin1997; bootstrap coverage from fix(gateway): block launchd restart helper loops #51003 by @joelbrilliant.)
  2. Label-independent, execution-aware detectionlaunchctl submit/bootstrap is blocked inside the gateway process regardless of label, via shlex-based command parsing (quoted prose and sub""mit obfuscation handled). This closes the neutral-label reproduction that defeats any label-anchored regex. (Cherry-picked from fix: block persistent gateway self-restart jobs #63272 by @John-Lussier, extended to cover bootstrap.)
  3. Referenced-script content inspection — direct, shell-wrapped, -c, relative-cwd, shebang-executable, nested, and remote-backend scripts are recursively scanned (bounded: 1 MiB, depth 8, regular files only, fail-closed on FIFOs/oversize) at both the terminal hard-block and cron script creation. (Cherry-picked from fix: block persistent gateway self-restart jobs #63272 by @John-Lussier.)

Credit

Regression tests (both sites)

  • launchctl submit -l com.foo -- /path/gateway — blocked in terminal (inside gateway) and in cron scripts
  • launchctl bootstrap gui/501 /tmp/com.foo.plist — bootstrap variant, both sites
  • exact macOS gateway lifecycle guard can be bypassed via launchctl submit, creating an infinite restart loop #62891 reported command incl. the line-continuation multi-line form
  • neutral-label ai.hermes.svc-reload-tmp reproduction
  • pass-through checks: submit/bootstrap allowed OUTSIDE the gateway; unrelated-label prose and non-gateway hermes services never falsely blocked
  • sabotage-verified: reverting the guard changes fails 7 tests

tests/hermes_cli/test_gateway_restart_loop.py: 78 passed · tests/cron: 373 passed · terminal tool suites: 133 passed · ruff clean.

Follow-up (not in this PR)

A restart-loop-breaker diagnostic that enumerates external launchd sources (launchctl list labels touching the gateway plus recently-submitted jobs) when gateway.restart_loop_guard trips would surface already-planted jobs like the 9,447-run one. That requires runtime gateway-side work beyond this guard-layer fix; recommend a separate issue referencing joelbrilliant's resolver suggestion in the #62891 thread.

Infographic

PR infographic

John-Lussier and others added 4 commits July 31, 2026 23:29
- Resolve guard cwd against get_session_cwd(session_key); fall back to env.cwd
  when no session record exists yet, matching current main's per-session cwd
  architecture.
- Make referenced-script reads backend-aware: local read first; if missing,
  fall back to env.execute('cat ...') for SSH/Modal/Daytona backends.
- Reuse the recursive scanner in check_gateway_lifecycle so nested cron
  wrapper scripts are caught, and resolve relative refs inside a script
  against that script's directory.
- Add regression tests for remote-backend reads, two-session cwd, and nested
  cron wrappers.

Verification: 80 passed tests/hermes_cli/test_gateway_restart_loop.py;
694 passed tests/cron; ruff + git diff --check clean.
…bel-independent detection

Extends the shared _GATEWAY_LIFECYCLE_PATTERN (used by BOTH the cron
creation-time guard in cron/lifecycle_guard.py and the terminal
execution-time hard-block in tools/terminal_tool.py) so Branch B covers
launchctl submit and bootstrap alongside kickstart/unload/load/stop/
restart, and normalizes POSIX shell line continuations before matching
so the exact multi-line reported shape in #62891 cannot slip past.

Also extends the execution-aware, label-independent detector
(contains_launchctl_submit_command, cherry-picked from #63272) to cover
launchctl bootstrap, since a neutral label like ai.hermes.svc-reload-tmp
defeats any label-anchored regex — the second production reproduction.

Regression tests cover both sites, including
`launchctl submit -l com.foo -- /path/gateway` and the bootstrap
variant, plus outside-gateway pass-through.

Branch B regex extension and continuation normalization drawn from
PR #62896; bootstrap coverage and test shapes drawn from PR #51003.

Co-authored-by: JackJin <1037461232@qq.com>
Co-authored-by: joelbrilliant <joelbrilliant1@gmail.com>
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on 5c7393c

ℹ️ Info

Desktop E2E visual evidence · View test artifacts · View job

3 visual diffs.

inline evidence upload failed.

Failed to upload diff-1508682a2ae8-boot-ready-diff.png with gh image (exit code 1): Error uploading /home/runner/work/_temp/e2e-evidence/diff-1508682a2ae8-boot-ready-diff.png: step 0 (get upload token): uploadToken not found on repo page — do you have write access to NousResearch/hermes-agent? (or, if NousResearch enforces SAML SSO, authorize at https://github.com/orgs/NousResearch/sso)

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cron Cron scheduler and job management tool/terminal Terminal execution and process management labels Aug 1, 2026
@teknium1
teknium1 merged commit 56cf874 into main Aug 1, 2026
40 checks passed
@teknium1
teknium1 deleted the fix/launchctl-submit-guard branch August 1, 2026 17:52
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 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.

macOS gateway lifecycle guard can be bypassed via launchctl submit, creating an infinite restart loop

3 participants