Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
try_launch_chrome_debug,
)
from hermes_cli.env_loader import load_hermes_dotenv
from utils import base_url_host_matches
from utils import base_url_host_matches, is_truthy_value

_hermes_home = get_hermes_home()
_project_env = Path(__file__).parent / '.env'
Expand Down Expand Up @@ -7146,7 +7146,7 @@ def _toggle_yolo(self):
import os
from hermes_cli.colors import Colors as _Colors

current = bool(os.environ.get("HERMES_YOLO_MODE"))
current = is_truthy_value(os.environ.get("HERMES_YOLO_MODE"))
if current:
os.environ.pop("HERMES_YOLO_MODE", None)
_cprint(
Expand Down
15 changes: 15 additions & 0 deletions tests/test_tui_gateway_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,21 @@ def test_config_busy_get_and_set(monkeypatch):
assert ("display.busy_input_mode", "interrupt") in writes


def test_config_set_yolo_process_scope_treats_false_like_env_as_disabled(monkeypatch):
monkeypatch.setenv("HERMES_YOLO_MODE", "false")

resp = server.handle_request(
{
"id": "1",
"method": "config.set",
"params": {"key": "yolo"},
}
)

assert resp["result"]["value"] == "1"
assert os.environ.get("HERMES_YOLO_MODE") == "1"


def test_config_get_statusbar_survives_non_dict_display(monkeypatch):
monkeypatch.setattr(server, "_load_cfg", lambda: {"display": "broken"})

Expand Down
27 changes: 27 additions & 0 deletions tests/tools/test_yolo_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,33 @@ def test_yolo_mode_empty_string_does_not_bypass(self, monkeypatch):
approval_callback=lambda *a: "deny")
assert not result["approved"]

@pytest.mark.parametrize("value", ["false", "False", "0", "off", "no"])
def test_false_like_yolo_values_do_not_bypass_dangerous_command(self, monkeypatch, value):
"""False-like env strings must not silently enable YOLO bypass."""
monkeypatch.setenv("HERMES_YOLO_MODE", value)
monkeypatch.setenv("HERMES_INTERACTIVE", "1")
monkeypatch.setenv("HERMES_SESSION_KEY", "test-session")

result = check_dangerous_command(
"rm -rf /tmp/stuff",
"local",
approval_callback=lambda *a: "deny",
)
assert not result["approved"]

@pytest.mark.parametrize("value", ["false", "False", "0", "off", "no"])
def test_false_like_yolo_values_do_not_bypass_combined_guard(self, monkeypatch, value):
"""Combined guard must treat false-like YOLO env strings as disabled."""
monkeypatch.setenv("HERMES_YOLO_MODE", value)
monkeypatch.setenv("HERMES_INTERACTIVE", "1")

result = check_all_command_guards(
"rm -rf /tmp/stuff",
"local",
approval_callback=lambda *a: "deny",
)
assert not result["approved"]

def test_session_scoped_yolo_only_bypasses_current_session(self, monkeypatch):
"""Gateway /yolo should only bypass approvals for the active session."""
monkeypatch.delenv("HERMES_YOLO_MODE", raising=False)
Expand Down
6 changes: 4 additions & 2 deletions tools/approval.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
from typing import Optional
from hermes_cli.config import cfg_get

from utils import is_truthy_value

logger = logging.getLogger(__name__)

# Per-thread/per-task gateway session identity.
Expand Down Expand Up @@ -802,7 +804,7 @@ def check_dangerous_command(command: str, env_type: str,

# --yolo: bypass all approval prompts. Gateway /yolo is session-scoped;
# CLI --yolo remains process-scoped via the env var for local use.
if os.getenv("HERMES_YOLO_MODE") or is_current_session_yolo_enabled():
if is_truthy_value(os.getenv("HERMES_YOLO_MODE")) or is_current_session_yolo_enabled():
return {"approved": True, "message": None}

is_dangerous, pattern_key, description = detect_dangerous_command(command)
Expand Down Expand Up @@ -927,7 +929,7 @@ def check_all_command_guards(command: str, env_type: str,
# --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 os.getenv("HERMES_YOLO_MODE") or is_current_session_yolo_enabled() or approval_mode == "off":
if is_truthy_value(os.getenv("HERMES_YOLO_MODE")) or is_current_session_yolo_enabled() or approval_mode == "off":
return {"approved": True, "message": None}

is_cli = os.getenv("HERMES_INTERACTIVE")
Expand Down
3 changes: 2 additions & 1 deletion tui_gateway/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

from hermes_constants import get_hermes_home
from hermes_cli.env_loader import load_hermes_dotenv
from utils import is_truthy_value
from tui_gateway.transport import (
StdioTransport,
Transport,
Expand Down Expand Up @@ -3421,7 +3422,7 @@ def _(rid, params: dict) -> dict:
enable_session_yolo(session["session_key"])
nv = "1"
else:
current = bool(os.environ.get("HERMES_YOLO_MODE"))
current = is_truthy_value(os.environ.get("HERMES_YOLO_MODE"))
if current:
os.environ.pop("HERMES_YOLO_MODE", None)
nv = "0"
Expand Down
Loading