Security and reliability fixes for gateway and subprocess

NousResearch/hermes-agent · cluster 33208 · agent

Data as of

Nothing open on GitHub. Fix merged in #54128. Check it covers your case.

The short answer

0open issues
0open PRs
5items grouped
Known?
Yes. 5 related items grouped here, 2 issues already closed.
Fix event
A fix was merged to main in PR #54128. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this partially resolved, deciding PR #54128. That is separate from the fix event.
Still open
Nothing open on GitHub.

This group covers security-focused patches including restricting gateway quick commands to authorized users, reducing shell=True subprocess usage, and a reliability fix replacing blocking sleep with async sleep in MCP auth polling.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #54128 fix(gateway): gate quick_commands through slash access policy (#44727)

    merged to main

    files: gateway/run.py, tests/gateway/test_slash_access_dispatch.py

    Show the diff
    • gateway/run.py
      @@ -8874,6 +8874,16 @@ async def _do_undo():
                   if not isinstance(quick_commands, dict):
                       quick_commands = {}
                   if command in quick_commands:
      +                # Quick commands are slash capabilities too — and type:exec
      +                # ones run a shell command in the gateway process. The early
      +                # gate above only fires for registry-known commands, so quick
      +                # commands (never in the registry) would otherwise reach this
      +                # dispatch sink unchecked. Apply the same admin/user policy to
      +                # the raw typed name here so non-admins can't invoke admin-only
      +                # quick commands. (#44727)
      +                _denied = self._check_slash_access(source, command)
      +                if _denied is not None:
      +                    return _denied
                       qcmd = quick_commands[command]
                       if qcmd.get("type") == "exec":
                           exec_cmd = qcmd.get("command", "")
    • tests/gateway/test_slash_access_dispatch.py
      @@ -315,6 +315,74 @@ def fake_is_known(name):
           assert "/myplugin is admin-only here" in result
       
       
      [redacted:email]
      +async def test_non_admin_denied_for_unlisted_quick_command_exec():
      +    """A non-admin must not reach the quick_commands exec sink for a command
      +    that isn't in user_allowed_commands. Regression for #44727 — quick
      +    commands are never in the gateway registry, so the early gate skips them;
      +    the sink gate must catch them."""
      +    runner = _make_runner(
      +        platform_extra={
      +            "allow_admin_from": ["111"],
      +            "user_allowed_commands": [],
      +        }
      +    )
      +    runner.config.quick_commands = {
      +        "limits": {"type": "exec", "command": "printf quick-command-bypass-confirmed"}
      +    }
      +
      +    result = await runner._handle_message(
      +        _make_event("/limits", _make_source(user_id="999"))
      +    )
      +
      +    assert result is not None
      +    assert "⛔" in result
      +    assert "/limits is admin-only here" in result
      +    assert "quick-command-bypass-confirmed" not in result
      +
      +
      [redacted:email]
      +async def test_listed_quick_command_runs_for_non_admin():
      +    """When the operator lists the quick command in user_allowed_commands, a
      +    non-admin can run it — the gate must allow, not blanket-deny."""
      +    runner = _make_runner(
      +        platform_extra={
      +            "allow_admin_from": ["111"],
      +            "user_allowed_commands": ["limits"],
      +        }
      +    )
      +    runner.config.quick_commands = {
      +        "limits": {"type": "exec", "command": "printf quick-command-allowed"}
      +    }
      +
      +    result = await runner._handle_message(
      +        _make_event("/limits", _make_source(user_id="999"))
      +    )
      +
      +    assert result == "quick-command-allowed"
      +
      +
      [redacted:email]
      +async def test_admin_runs_quick_command_when_gating_enabled():
      +    """An admin runs the quick command even under an enabled gate with an
      +    empty user_allowed_commands list."""
      +    runner = _make_runner(
      +        platform_extra={
      +            "allow_admin_from": ["111"],
      +            "user_allowed_commands": [],
      +        }
      +    )
      +    runner.config.quick_commands = {
      +        "limits": {"type": "exec", "command": "printf quick-command-admin"}
      +    }
      +
      +    result = await runner._handle_message(
      +        _make_event("/limits", _make_source(user_id="111"))
      +    )
      +
      +    assert result == "quick-command-admin"
      +
      +
       # ---------------------------------------------------------------------------
       # Running-agent fast-path gating — admin/user split must hold even when an
       # agent is already running. The fast-path block in _handle_message dispatches

2 PRs closed without merging

From the close comments: merged via another change 2

  • #44791 fix(gateway): gate quick commands with slash access policy · merged via another change → 9844243, #54128, #44727
  • #45536 fix(security): gate gateway quick commands · merged via another change → 9844243, #54128, #44791
Closed issues · 2
  • Closed#33208Audit Fixes: 15 targeted bug fixes (security, perf, reliability)
  • Closed#44727[Security] Hermes gateway quick commands bypass admin-only slash command restrictions
How this cluster was grouped
  • Link evidence (partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
  • Members outside that structural core may belong to a different underlying problem.
  • Pairwise link density 0.34: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Subsystem
agent
Merged PRs
#54128
Closed issues
2
Attempts
2 PRs closed without merging
Fix link checked
28 Jun 2026

For your agent

The same facts, typed.