fix(approval): harden YOLO bypass, LLM parsing, auto-approve audit, pipe pattern (#23835)

* fix(approval): harden YOLO bypass, LLM parsing, auto-approve audit, pipe pattern

- BUG-009 (CRITICAL): freeze HERMES_YOLO_MODE at module import via
  _YOLO_MODE_FROZEN; prevents skills/prompt-injection from calling
  os.environ["HERMES_YOLO_MODE"]="true" at runtime to bypass all checks
- BUG-002 (HIGH): replace substring "APPROVE" in answer with exact
  answer == "APPROVE" in _smart_approve; prompt already requests exactly
  one word, substring match was exploitable via verbose LLM responses
- BUG-001 (MEDIUM): add logger.warning for every dangerous command that
  auto-approves in non-interactive non-gateway context; makes silent
  approvals visible in audit logs without breaking script behavior
- BUG-008 (LOW): expand curl/wget pipe pattern to cover | /bin/bash and
  | bash -c variants, not just | sh / | bash

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(approval): add missing is_truthy_value import + fix yolo test patches

_YOLO_MODE_FROZEN uses is_truthy_value() from utils — import was missing.
Tests that set HERMES_YOLO_MODE via monkeypatch.setenv() no longer work
because the value is frozen at import time; update them to patch the
module-level flag directly via monkeypatch.setattr().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Rodrigo
2026-05-25 03:35:33 -07:00
committed by GitHub
co-authored by Claude Sonnet 4.6
parent 3ab7e2aa91
commit 4cb3eb03c7
2 changed files with 20 additions and 10 deletions
+5 -5
View File
@@ -55,8 +55,8 @@ class TestYoloMode:
assert not result["approved"]
def test_dangerous_command_approved_in_yolo_mode(self, monkeypatch):
"""With HERMES_YOLO_MODE, dangerous (non-hardline) commands are auto-approved."""
monkeypatch.setenv("HERMES_YOLO_MODE", "1")
"""With HERMES_YOLO_MODE, dangerous commands are auto-approved."""
monkeypatch.setattr(approval_module, "_YOLO_MODE_FROZEN", True)
monkeypatch.setenv("HERMES_INTERACTIVE", "1")
monkeypatch.setenv("HERMES_SESSION_KEY", "test-session")
@@ -68,8 +68,8 @@ class TestYoloMode:
assert result["message"] is None
def test_yolo_mode_works_for_all_patterns(self, monkeypatch):
"""Yolo mode bypasses dangerous patterns (except the hardline floor)."""
monkeypatch.setenv("HERMES_YOLO_MODE", "1")
"""Yolo mode bypasses all dangerous patterns, not just some."""
monkeypatch.setattr(approval_module, "_YOLO_MODE_FROZEN", True)
monkeypatch.setenv("HERMES_INTERACTIVE", "1")
# Dangerous but recoverable — yolo should bypass.
@@ -90,7 +90,7 @@ class TestYoloMode:
def test_combined_guard_bypasses_yolo_mode(self, monkeypatch):
"""The new combined guard should preserve yolo bypass semantics."""
monkeypatch.setenv("HERMES_YOLO_MODE", "1")
monkeypatch.setattr(approval_module, "_YOLO_MODE_FROZEN", True)
monkeypatch.setenv("HERMES_INTERACTIVE", "1")
called = {"value": False}