Desktop app auto-approves dangerous commands in local chat — no approval prompt (Telegram shows one)

Author: HarshalRathoreCreated Aug 8, 2026Updated Sep 9, 2026

Summary

Hermes One desktop app silently auto-approves every dangerous command in local (dashboard) chat — no approval prompt is ever shown. In Telegram/Discord (gateway platforms) the same command shows inline approve buttons (once / session / always / deny). This defeats approvals.mode: manual on the desktop surface.

Evidence

The dashboard backend already implements the full approval protocol correctly:

  • tui_gateway/server.py _emit_approval_request emits an approval.request WS event with choices: ["once", "session", "always", "deny"] (subset depending on allow_permanent).
  • tui_gateway/methods_prompt.py approval.respond RPC resolves the pending approval via resolve_gateway_approval(session_key, choice).

The desktop app then swallows it in src/main/hermes.ts:

  1. Local chat (TUI gateway WS)sendMessageViaTuiGateway:
typescript
if (event.type === "approval.request") {
  // Match the existing local chat posture: Hermes One does not expose a
  // mid-stream approval dialog, so answer the dashboard protocol once and
  // keep the transcript focused on the resulting tool call/result events.
  void client.request("approval.respond", {
    session_id: activeSessionId,
    choice: "once",
    all: false,
  }, 30_000)...

Every approval is answered choice: "once" — the command runs with no user interaction.

  1. API-stream pathopenEventStream:
typescript
if (eventName === "approval.request") {
  stopRunAndFallback();
}

The run is stopped and the chat silently downgrades to plain completions.

  1. The renderer's Approve/Deny bar (chat-approval-bar in the chat chunk) is only wired to the legacy flow: it appears after a response finishes, gated by the text heuristic APPROVAL_RE (/requires? (your )?approval|\/approve.*\/deny|.../). It is never triggered by the real mid-run approval.request event.

Expected behavior

A visible approval prompt with the same choices the gateway platforms offer (approve once / approve for session / always allow / deny), shown mid-run when approval.request arrives, answered via approval.respond. The server protocol is already in place — this is an app-side UI gap.

Impact

  • approvals.mode: manual is silently ineffective on the desktop surface (commands in DANGEROUS_PATTERNS run unattended).
  • Hardline blocks still apply, so this is not a full bypass — but every recoverable flagged command (curl|sh, chmod +x, sensitive-path writes, etc.) executes without consent.