diff --git a/hermes_cli/bang_shell.py b/hermes_cli/bang_shell.py index 706f1e149e..6753d43283 100644 --- a/hermes_cli/bang_shell.py +++ b/hermes_cli/bang_shell.py @@ -90,13 +90,11 @@ def _bang_env() -> dict: """Environment for a bang command with Hermes-managed secrets filtered. The CLI process holds every provider API key; a user-typed command may still run a third-party - script, so reuse the sanitizer ``quick_commands`` and the local terminal backend use. + script, so reuse the sanitizer ``quick_commands`` and the local terminal backend use. If that + boundary is unavailable, let the launch fail rather than passing the CLI's full environment. """ - try: - from tools.environments.local import build_subprocess_env - return build_subprocess_env() # == _sanitize_subprocess_env(os.environ.copy()) - except Exception: - return os.environ.copy() # tools package unimportable: run the user's command anyway + from tools.environments.local import build_subprocess_env + return build_subprocess_env() def run_bang_command(command: str, *, cwd: Optional[str] = None, timeout: int = DEFAULT_TIMEOUT, writer=None) -> int: diff --git a/tests/hermes_cli/test_bang_shell_mode.py b/tests/hermes_cli/test_bang_shell_mode.py index 63fedde30d..ae74a1a0ed 100644 --- a/tests/hermes_cli/test_bang_shell_mode.py +++ b/tests/hermes_cli/test_bang_shell_mode.py @@ -5,6 +5,7 @@ invoked for a dangerous command, that non-zero exit codes surface, and the load-bearing invariant: a bang command leaves conversation_history byte-identical because it never becomes a turn. """ +import builtins import copy import json import os @@ -121,6 +122,23 @@ class TestBangExecution: assert code == 0 assert "ok" in lines + def test_unavailable_environment_sanitizer_prevents_launch(self): + lines = [] + real_import = builtins.__import__ + + def block_sanitizer(name, *args, **kwargs): + if name == "tools.environments.local": + raise ImportError("environment sanitizer unavailable") + return real_import(name, *args, **kwargs) + + with patch("builtins.__import__", block_sanitizer), \ + patch("hermes_cli.bang_shell.subprocess.Popen") as popen: + code = run_bang_command("echo must-not-run", writer=lines.append) + + assert code == 127 + popen.assert_not_called() + assert any("failed to run command" in line for line in lines) + # ── CLI handler: approval gate, usage hint, exit codes ─────────────────────