Summary
The shell.exec JSON-RPC method in tui_gateway/server.py gates execution by calling tools.approval.detect_dangerous_command and, on a "dangerous" verdict, returning an error. Because that detector is the same bypassable regex denylist tracked in the sibling issue, every bypass payload passes the shell.exec gate and is handed straight to subprocess.run(cmd, shell=True, …). This is a distinct, network-relevant call path with the same root cause.
Affected code (current main, ef3a650f05d2)
tui_gateway/server.py:7815 — @method("shell.exec"):
@method("shell.exec")
def _(rid, params: dict) -> dict:
cmd = params.get("command", "")
...
from tools.approval import detect_dangerous_command
is_dangerous, _, desc = detect_dangerous_command(cmd)
if is_dangerous:
return _err(rid, 4005, f"blocked: {desc}. ...")
...
r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30, cwd=os.getcwd())
Note also the except ImportError: pass — if the import fails, the gate is skipped entirely (fail-open secondary path).
Reproduction
Any payload from the sibling denylist issue (r\m -rf …, $(echo rm) -rf …, ${0/x/r}m -rf …) sent as the command param of a shell.exec request returns no 4005 blocked error and executes under the hermes user. Confirmed against detect_dangerous_command on current main:
from tools.approval import detect_dangerous_command
detect_dangerous_command(r"r\m -rf /home/victim") # → (False, None, None) → shell.exec proceeds
Impact
Arbitrary command execution via the TUI gateway protocol, bypassing the only safety check on this path. CWE-184. Severity follows the denylist root cause (Critical / RCE).
Suggested remediation
Fix the shared classifier (see sibling issue — structural shlex tokenization instead of regex). Additionally, replace except ImportError: pass with a fail-closed error so a missing approval module cannot silently disable the gate.
Prior private disclosure & status
Reported privately via GitHub Security Advisory on 2026-04-23 (GHSA-2jg5-795w-3rv5, "tui_gateway shell.exec inherits regex-denylist bypass", rated High). The advisory was closed on 2026-05-14 without a fix, CVE, or publication. The code path still reproduces on current main (ef3a650f05d2, 2026-06-01). Filing publicly so it has a tracked record. Shares a root cause with the tools/approval.py denylist issue.
Summary
The
shell.execJSON-RPC method intui_gateway/server.pygates execution by callingtools.approval.detect_dangerous_commandand, on a "dangerous" verdict, returning an error. Because that detector is the same bypassable regex denylist tracked in the sibling issue, every bypass payload passes theshell.execgate and is handed straight tosubprocess.run(cmd, shell=True, …). This is a distinct, network-relevant call path with the same root cause.Affected code (current
main,ef3a650f05d2)tui_gateway/server.py:7815—@method("shell.exec"):Note also the
except ImportError: pass— if the import fails, the gate is skipped entirely (fail-open secondary path).Reproduction
Any payload from the sibling denylist issue (
r\m -rf …,$(echo rm) -rf …,${0/x/r}m -rf …) sent as thecommandparam of ashell.execrequest returns no4005 blockederror and executes under the hermes user. Confirmed againstdetect_dangerous_commandon currentmain:Impact
Arbitrary command execution via the TUI gateway protocol, bypassing the only safety check on this path. CWE-184. Severity follows the denylist root cause (Critical / RCE).
Suggested remediation
Fix the shared classifier (see sibling issue — structural
shlextokenization instead of regex). Additionally, replaceexcept ImportError: passwith a fail-closed error so a missing approval module cannot silently disable the gate.Prior private disclosure & status
Reported privately via GitHub Security Advisory on 2026-04-23 (
GHSA-2jg5-795w-3rv5, "tui_gatewayshell.exec inherits regex-denylist bypass", rated High). The advisory was closed on 2026-05-14 without a fix, CVE, or publication. The code path still reproduces on currentmain(ef3a650f05d2, 2026-06-01). Filing publicly so it has a tracked record. Shares a root cause with thetools/approval.pydenylist issue.