Title: Hermes gateway quick commands bypass admin-only slash command restrictions
Description:
Hermes gateway enforces allow_admin_from and user_allowed_commands only for registered gateway slash commands. Operator-configured quick_commands are dispatched later on a separate path without the same authorization check, which allows an authenticated allowlisted non-admin messaging user to invoke admin-only quick commands, including type: exec commands that run in the gateway process.
The vulnerable behavior is in gateway/run.py. During inbound slash-command handling, Hermes resolves the typed command and applies _check_slash_access(...) only when the canonical command is present in the gateway command registry:
if command and canonical and is_gateway_known_command(canonical):
_denied = self._check_slash_access(source, canonical)
if _denied is not None:
return _deniedThat gate is not applied to config-backed quick_commands. Later in the same handler, Hermes looks up the raw typed command name in quick_commands and executes type: exec commands directly:
if command in quick_commands:
qcmd = quick_commands[command]
if qcmd.get("type") == "exec":
proc = await asyncio.create_subprocess_shell(...)This creates a policy split. Registered slash commands such as /stop are denied for the same non-admin sender under the configured policy, but a non-registered quick command such as /limits still reaches the execution sink and returns command output.
I verified this with the real GatewayRunner._handle_message(...) entrypoint using real SessionSource and MessageEvent objects. The test policy was:
allow_admin_from: ["111"]user_allowed_commands: []quick_commands.limits = {type: exec, command: "printf quick-command-bypass-confirmed"}
Under that policy, a non-admin sender 999 received quick-command-bypass-confirmed from /limits, while the control input /stop returned the expected denial message:
⛔ /stop is admin-only here. No slash commands are enabled for non-admins on this platform.
- Hermes Agent checkout from the canonical upstream repository.
- Python virtual environment available at
./.venv. - A configuration equivalent to:
allow_admin_from: ["111"]user_allowed_commands: []quick_commands.limits = {type: exec, command: "printf quick-command-bypass-confirmed"}
- Attacker position: authenticated allowlisted non-admin gateway user in DM scope.
- Download the verification PoC from: verification_test.py
- Download the control PoC from: control-non-admin-builtin-denied.py
- From the repository root, run the verification case:
./.venv/bin/python llm-enhance/cve-finding/similar/Auth-Privilege/GHSA-2hm8-rqrm-xfjq-quick-commands-slash-access-bypass-exp/verification_test.py - Observe the verification output includes:
quick-command-bypass-confirmed - Run the control case under the same policy:
./.venv/bin/python llm-enhance/cve-finding/similar/Auth-Privilege/GHSA-2hm8-rqrm-xfjq-quick-commands-slash-access-bypass-exp/control-non-admin-builtin-denied.py - Observe the control output includes an admin-only denial for
/stop.
Verification log excerpt:
{
"mode": "Integration-Test",
"input": "/limits",
"user_id": "999",
"policy": {
"allow_admin_from": ["111"],
"user_allowed_commands": []
},
"observed_result": "quick-command-bypass-confirmed"
}Control log excerpt:
{
"mode": "Integration-Test",
"input": "/stop",
"user_id": "999",
"observed_result": "⛔ /stop is admin-only here. No slash commands are enabled for non-admins on this platform. Ask an admin to add you to allow_admin_from or to set user_allowed_commands."
}This is an authorization bypass on Hermes' messaging-gateway slash-command surface. Operators who rely on allow_admin_from and user_allowed_commands to separate admin-only and non-admin slash capabilities can unintentionally expose configured quick commands to non-admin gateway users. When the quick command uses type: exec, the non-admin user can trigger shell command execution in the gateway process context, subject to the operator-defined command body. That can expose sensitive operational data, modify system state, or disrupt service depending on what the quick command does.
- Ecosystem: pip
- Package name: hermes-agent
- Affected versions: <= 2026.6.5
- Patched versions:
- Severity: High
- Vector string: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
- CWE: CWE-863: Incorrect Authorization
| Permalink | Description |
|---|---|
| https://github.com/NousResearch/hermes-agent/blob/3c231eb3979ab9c57d5cd6d02f1d577a3b718b43/gateway/run.py#L8092-L8101 | Hermes documents and enforces slash access control only for commands that resolve through is_gateway_known_command(canonical), leaving non-registered quick-command names outside the gate. |
| https://github.com/NousResearch/hermes-agent/blob/3c231eb3979ab9c57d5cd6d02f1d577a3b718b43/gateway/run.py#L8344-L8367 | The quick_commands dispatch path executes type: exec commands with asyncio.create_subprocess_shell(...) and returns output without re-running _check_slash_access(...) for the typed slash command. |
| https://github.com/NousResearch/hermes-agent/blob/3c231eb3979ab9c57d5cd6d02f1d577a3b718b43/gateway/run.py#L10231-L10271 | _check_slash_access(...) is the intended admin/non-admin policy primitive. Its denial message confirms that non-admin users with empty user_allowed_commands should not be able to invoke admin-only slash capabilities. |