fix(approval): harden YOLO mode env parsing against quoted-bool strings

This commit is contained in:
hharry11 2026-04-27 06:42:32 +03:00 committed by Teknium
parent 158eb32686
commit 24130b7e53
5 changed files with 50 additions and 5 deletions

4
cli.py
View File

@ -85,7 +85,7 @@ from hermes_cli.browser_connect import (
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'
@ -7146,7 +7146,7 @@ class HermesCLI:
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(

View File

@ -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"})

View File

@ -125,6 +125,33 @@ class TestYoloMode:
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)

View File

@ -19,6 +19,8 @@ import unicodedata
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.
@ -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)
@ -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")

View File

@ -17,6 +17,7 @@ from typing import Any, Optional
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,
@ -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"