fix(cli): fail closed without bang shell sanitizer

This commit is contained in:
fangliquan
2026-09-20 14:32:33 +08:00
committed by Teknium
parent e8a6bcada9
commit 7df20a670d
2 changed files with 22 additions and 6 deletions

View File

@@ -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:

View File

@@ -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 ─────────────────────