# NousResearch/hermes-agent · Security and reliability fixes for gateway and subprocess

## Open now

- 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.

## TL;DR

- 5 member(s): 0 open issue(s), 2 closed issue(s), 0 open PR(s), 1 merged PR(s), 2 PR(s) closed without merging.
- A fix was merged to main in PR #54128. Verify it addresses your case.
- **Recommended:** read PR #54128 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `partially_resolved` · deciding PR(s): `pr-54128`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_PARTIAL`): 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 member 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.

## Closed / duplicate issues

- #33208: Audit Fixes: 15 targeted bug fixes (security, perf, reliability)
- #44727: \[Security\] Hermes gateway quick commands bypass admin-only slash command restrictions

## Prior & competing PRs

- **#54128: fix(gateway): gate quick_commands through slash access policy (#44727)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `gateway/run.py`, `tests/gateway/test_slash_access_dispatch.py`
  - fix hunks:
  - `gateway/run.py`

    ```diff
    @@ -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`

    ```diff
    @@ -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
    ```
- **#44791: fix(gateway): gate quick commands with slash access policy**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `9844243b1`, `#54128`, `#44727`
- **#45536: fix(security): gate gateway quick commands**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `9844243b1`, `#54128`, `#44791`

## Fix status

- Merged to main in PR #54128 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-06-28`.
- Subsystem: `agent`
