hermes - 💡(How to fix) Fix CLI /yolo (in-chat) does not bypass dangerous command approvals — env var freeze + missing enable_session_yolo call [2 pull requests]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Root Cause

The CLI's in-chat /yolo command toggles os.environ["HERMES_YOLO_MODE"] but has no effect because tools/approval.py:_YOLO_MODE_FROZEN is captured at module-import time. By the time the user reaches the /yolo prompt in a running CLI session, tools.approval has already been imported (it's pulled in transitively by the terminal tool / tool registry at CLI startup), so flipping the env var afterward never changes the frozen flag.

Fix Action

Fixed

Code Example

# Freeze YOLO mode at module import time. Reading os.environ on every call
# would allow any skill running inside the process to set this variable and
# instantly bypass all approval checks — a prompt-injection escalation path.
_YOLO_MODE_FROZEN: bool = is_truthy_value(os.getenv("HERMES_YOLO_MODE", ""))

---

def _toggle_yolo(self):
    """Toggle YOLO mode — skip all dangerous command approval prompts."""
    import os
    from hermes_cli.colors import Colors as _Colors

    current = is_truthy_value(os.environ.get("HERMES_YOLO_MODE"))
    if current:
        os.environ.pop("HERMES_YOLO_MODE", None)
        _cprint(
            f"  ⚠ YOLO mode {_Colors.BOLD}{_Colors.RED}OFF{_Colors.RESET}"
            " — dangerous commands will require approval."
        )
    else:
        os.environ["HERMES_YOLO_MODE"] = "1"
        _cprint(
            f"  ⚡ YOLO mode {_Colors.BOLD}{_Colors.GREEN}ON{_Colors.RESET}"
            " — all commands auto-approved. Use with caution."
        )

---

async def _handle_yolo_command(self, event: MessageEvent) -> Union[str, EphemeralReply]:
    from tools.approval import (
        disable_session_yolo, enable_session_yolo, is_session_yolo_enabled,
    )
    session_key = self._session_key_for_source(event.source)
    current = is_session_yolo_enabled(session_key)
    if current:
        disable_session_yolo(session_key)
        return EphemeralReply(t("gateway.yolo.disabled"))
    else:
        enable_session_yolo(session_key)
        return EphemeralReply(t("gateway.yolo.enabled"))

---

# --yolo or approvals.mode=off: bypass all approval prompts.
# Gateway /yolo is session-scoped; CLI --yolo remains process-scoped.
approval_mode = _get_approval_mode()
if _YOLO_MODE_FROZEN or is_current_session_yolo_enabled() or approval_mode == "off":
    return {"approved": True, "message": None}

---

$ hermes
> /yolo
YOLO mode ON — all commands auto-approved. Use with caution.

> run `rm -rf /tmp/foo` to clean up

This command is potentially dangerous (...). Asking for approval...
RAW_BUFFERClick to expand / collapse

Bug Description

The CLI's in-chat /yolo command toggles os.environ["HERMES_YOLO_MODE"] but has no effect because tools/approval.py:_YOLO_MODE_FROZEN is captured at module-import time. By the time the user reaches the /yolo prompt in a running CLI session, tools.approval has already been imported (it's pulled in transitively by the terminal tool / tool registry at CLI startup), so flipping the env var afterward never changes the frozen flag.

The /yolo CLI handler also does not call enable_session_yolo() the way the gateway and TUI handlers do, so the per-session bypass path can't fire either.

Net effect: /yolo toggles the status-bar "⚠ YOLO" badge ON but every dangerous command still hits an approval prompt or is denied. The only working full-bypass routes are hermes --yolo, HERMES_YOLO_MODE=1 hermes ..., and hermes config set approvals.mode off — all of which are evaluated before module import (the env-var routes) or read fresh from config on every call (the config route).

Code Pointers

The freeze (intentional, security hardening):

tools/approval.py:26-29

# Freeze YOLO mode at module import time. Reading os.environ on every call
# would allow any skill running inside the process to set this variable and
# instantly bypass all approval checks — a prompt-injection escalation path.
_YOLO_MODE_FROZEN: bool = is_truthy_value(os.getenv("HERMES_YOLO_MODE", ""))

The broken CLI handler (env-var-only, no enable_session_yolo call):

cli.py:9610-9627

def _toggle_yolo(self):
    """Toggle YOLO mode — skip all dangerous command approval prompts."""
    import os
    from hermes_cli.colors import Colors as _Colors

    current = is_truthy_value(os.environ.get("HERMES_YOLO_MODE"))
    if current:
        os.environ.pop("HERMES_YOLO_MODE", None)
        _cprint(
            f"  ⚠ YOLO mode {_Colors.BOLD}{_Colors.RED}OFF{_Colors.RESET}"
            " — dangerous commands will require approval."
        )
    else:
        os.environ["HERMES_YOLO_MODE"] = "1"
        _cprint(
            f"  ⚡ YOLO mode {_Colors.BOLD}{_Colors.GREEN}ON{_Colors.RESET}"
            " — all commands auto-approved. Use with caution."
        )

For comparison, the gateway handler does it correctly:

gateway/run.py:12074-12089

async def _handle_yolo_command(self, event: MessageEvent) -> Union[str, EphemeralReply]:
    from tools.approval import (
        disable_session_yolo, enable_session_yolo, is_session_yolo_enabled,
    )
    session_key = self._session_key_for_source(event.source)
    current = is_session_yolo_enabled(session_key)
    if current:
        disable_session_yolo(session_key)
        return EphemeralReply(t("gateway.yolo.disabled"))
    else:
        enable_session_yolo(session_key)
        return EphemeralReply(t("gateway.yolo.enabled"))

tui_gateway/server.py:4244-4267 does the same enable_session_yolo(session["session_key"]) thing on the TUI path.

The actual check that gates terminal commands:

tools/approval.py:1086-1090

# --yolo or approvals.mode=off: bypass all approval prompts.
# Gateway /yolo is session-scoped; CLI --yolo remains process-scoped.
approval_mode = _get_approval_mode()
if _YOLO_MODE_FROZEN or is_current_session_yolo_enabled() or approval_mode == "off":
    return {"approved": True, "message": None}

For the CLI /yolo handler, none of the three conditions are true after the toggle:

  • _YOLO_MODE_FROZEN was captured at module import (before /yolo ran)
  • is_current_session_yolo_enabled() returns False because enable_session_yolo was never called
  • approval_mode == "off" requires the persisted config setting

Repro

$ hermes
> /yolo
  ⚡ YOLO mode ON — all commands auto-approved. Use with caution.

> run `rm -rf /tmp/foo` to clean up

  ⚠ This command is potentially dangerous (...). Asking for approval...

The status bar reads ⚠ YOLO but every dangerous command is still gated.

Expected Behavior

/yolo from inside the CLI should behave identically to:

  • hermes --yolo at startup
  • /yolo from inside the TUI
  • /yolo from a gateway platform (telegram, discord, etc.)

All four should toggle approval bypass for the active session, and the change should be visible to check_all_command_guards on the very next dangerous command.

Proposed Fix

The CLI /yolo handler should call enable_session_yolo(session_key) / disable_session_yolo(session_key) instead of mutating os.environ["HERMES_YOLO_MODE"]. This requires:

  1. The CLI agent run path to bind a per-session approval key via set_current_session_key() so is_current_session_yolo_enabled() resolves against the same key as the toggle stored under. (The gateway and TUI already do this; the interactive CLI does not.)
  2. _toggle_yolo() to read/write the session-yolo set instead of the env var.
  3. The status-bar yolo_active = bool(os.getenv("HERMES_YOLO_MODE")) check (cli.py:3750, cli.py:3811) updated to read from the session-yolo helper too, so the badge reflects reality.

Keep the env-var path working at process start (it's how --yolo propagates to subprocesses and skills), but stop treating runtime env mutation as a YOLO toggle source.

Related

  • #31925 / #31933 — dashboard UI's approvals.mode dropdown shows stale ["ask", "yolo", "deny"] instead of ["manual", "smart", "off"]. Same family of bug (UI surface that doesn't actually bypass approvals).
  • #33605 — closed, status-bar treated HERMES_YOLO_MODE=false as YOLO on.
  • #32705 — fix for YOLO bypass in computer_use tool; different scope, doesn't touch the CLI /yolo path.

Environment

  • Hermes Agent v0.14.0 (2026.5.16) [a1eaad2f]
  • Reproduced via code inspection on origin/main at 0554ef1aa
  • Reporter confirmed repro on macOS Darwin 24.6.0 + Ubuntu server
  • Provider/model not relevant — this is a local terminal approval-layer bug

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING