feat: make smart approvals the default (#62661)

This commit is contained in:
Teknium 2026-07-12 00:25:55 -07:00 committed by GitHub
parent 2043436af1
commit 62a76bd3d5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 65 additions and 29 deletions

View file

@ -2523,15 +2523,15 @@ DEFAULT_CONFIG = {
},
# Approval mode for dangerous commands:
# manual — always prompt the user (default)
# smart — use auxiliary LLM to auto-approve low-risk commands, prompt for high-risk
# manual — always prompt the user
# smart — use auxiliary LLM to auto-approve low-risk commands (default)
# off — skip all approval prompts (equivalent to --yolo)
#
# cron_mode — what to do when a cron job hits a dangerous command:
# deny — block the command and let the agent find another way (default, safe)
# approve — auto-approve all dangerous commands in cron jobs
"approvals": {
"mode": "manual",
"mode": "smart",
"timeout": 60,
"cron_mode": "deny",
# User-defined deny rules: fnmatch globs matched against terminal

View file

@ -552,10 +552,10 @@ hermes config set privacy.redact_pii false # disable (default)
### Command approval prompts
By default (`approvals.mode: manual`), Hermes prompts the user before running shell commands flagged as destructive (`rm -rf`, `git reset --hard`, etc.). The modes are:
By default (`approvals.mode: smart`), Hermes asks an auxiliary LLM to assess shell commands flagged as destructive (`rm -rf`, `git reset --hard`, etc.). The modes are:
- `manual` — always prompt (default)
- `smart` — use an auxiliary LLM to auto-approve low-risk commands, prompt on high-risk
- `smart` — auto-approve a low-risk command once, deny high-risk commands, and prompt when uncertain (default)
- `manual` — always prompt
- `off` — skip all approval prompts (equivalent to `--yolo`)
```bash

View file

@ -55,6 +55,11 @@ class TestApprovalModeParsing:
class TestSmartApproval:
def test_smart_is_the_default_approval_mode(self):
from hermes_cli.config import DEFAULT_CONFIG
assert DEFAULT_CONFIG["approvals"]["mode"] == "smart"
def test_smart_approval_uses_call_llm(self):
response = SimpleNamespace(
choices=[SimpleNamespace(message=SimpleNamespace(content="APPROVE"))]
@ -68,6 +73,35 @@ class TestSmartApproval:
assert mock_call.call_args.kwargs["temperature"] == 0
assert mock_call.call_args.kwargs["max_tokens"] == 16
def test_smart_approval_does_not_allowlist_the_pattern_for_session(self, monkeypatch):
session_key = "test-smart-per-command"
command = "python -c \"print('hello')\""
dangerous, pattern_key, _ = detect_dangerous_command(command)
assert dangerous is True
monkeypatch.setenv("HERMES_SESSION_KEY", session_key)
monkeypatch.setenv("HERMES_EXEC_ASK", "1")
monkeypatch.delenv("HERMES_CRON_SESSION", raising=False)
monkeypatch.setattr(
approval_module,
"_get_approval_config",
lambda: {"mode": "smart"},
)
monkeypatch.setattr(approval_module, "_YOLO_MODE_FROZEN", False)
monkeypatch.setattr(approval_module, "_smart_approve", lambda *_: "approve")
monkeypatch.setattr(
"tools.tirith_security.check_command_security",
lambda _command: {"action": "allow", "findings": [], "summary": ""},
)
approval_module.clear_session(session_key)
approval_module._permanent_approved.clear()
result = approval_module.check_all_command_guards(command, "local")
assert result["approved"] is True
assert result["smart_approved"] is True
assert is_approved(session_key, pattern_key) is False
class TestDetectDangerousRm:
def test_rm_rf_detected(self):
@ -2140,11 +2174,13 @@ class TestApprovalTimeoutIsNotConsent:
assert "rephrase" in msg.lower()
assert "different command" in msg.lower()
def test_explicit_deny_carries_same_no_consent_shape(self):
def test_explicit_deny_carries_same_no_consent_shape(self, monkeypatch):
"""An explicit /deny must produce the same shape as timeout —
the agent should treat both identically."""
from tools import approval as mod
self._force_short_timeout(monkeypatch, seconds=60)
notified = []
mod.register_gateway_notify(self.SESSION_KEY, lambda data: notified.append(data))

View file

@ -2745,9 +2745,9 @@ def check_all_command_guards(command: str, env_type: str,
combined_desc_for_llm = "; ".join(desc for _, desc, _ in warnings)
verdict = _smart_approve(command, combined_desc_for_llm)
if verdict == "approve":
# Auto-approve and grant session-level approval for these patterns
for key, _, _ in warnings:
approve_session(session_key, key)
# Approve this command only. Pattern-level persistence would let one
# benign command suppress review of later commands that happen to
# match the same broad detector category.
logger.debug("Smart approval: auto-approved '%s' (%s)",
command[:60], combined_desc_for_llm)
return {"approved": True, "message": None,

View file

@ -1879,13 +1879,13 @@ Control how Hermes handles potentially dangerous commands:
```yaml
approvals:
mode: manual # manual | smart | off
mode: smart # smart | manual | off
```
| Mode | Behavior |
|------|----------|
| `manual` (default) | Prompt the user before executing any flagged command. In the CLI, shows an interactive approval dialog. In messaging, queues a pending approval request. |
| `smart` | Use an auxiliary LLM to assess whether a flagged command is actually dangerous. Low-risk commands are auto-approved with session-level persistence. Genuinely risky commands are escalated to the user. |
| `smart` (default) | Use an auxiliary LLM to assess whether a flagged command is actually dangerous. Low-risk commands are auto-approved for that command only. Genuinely risky commands are denied; uncertain decisions escalate to the user. |
| `manual` | Prompt the user before executing any flagged command. In the CLI, shows an interactive approval dialog. In messaging, queues a pending approval request. |
| `off` | Skip all approval checks. Equivalent to `HERMES_YOLO_MODE=true`. **Use with caution.** |
Smart mode is particularly useful for reducing approval fatigue — it lets the agent work more autonomously on safe operations while still catching genuinely destructive commands.

View file

@ -30,7 +30,7 @@ The approval system supports three modes, configured via `approvals.mode` in `~/
```yaml
approvals:
mode: manual # manual | smart | off
mode: smart # smart | manual | off
timeout: 60 # seconds to wait for user response (default: 60)
cron_mode: deny # deny | approve — what cron jobs do when they hit a dangerous command
mcp_reload_confirm: true # /reload-mcp asks before invalidating the MCP tool cache
@ -41,7 +41,7 @@ The full set of keys:
| Key | Default | What it controls |
|---|---|---|
| `mode` | `manual` | Approval policy for dangerous shell commands — see the table below. |
| `mode` | `smart` | Approval policy for dangerous shell commands — see the table below. |
| `timeout` | `60` | Seconds Hermes waits for an approval reply before timing out. |
| `cron_mode` | `deny` | How [cron jobs](./features/cron.md) behave headlessly when they trigger a dangerous-command prompt. `deny` blocks the command (the agent must find another path); `approve` auto-approves everything in cron context. |
| `mcp_reload_confirm` | `true` | When true, `/reload-mcp` asks before rebuilding the MCP tool set. Rebuilding invalidates the provider prompt cache (tool schemas live in the system prompt), so the next message re-sends full input tokens. Users who click **Always Approve** flip this key to `false`. |
@ -49,8 +49,8 @@ The full set of keys:
| Mode | Behavior |
|------|----------|
| **manual** (default) | Always prompt the user for approval on dangerous commands |
| **smart** | Use an auxiliary LLM to assess risk. Low-risk commands (e.g., `python -c "print('hello')"`) are auto-approved. Genuinely dangerous commands are auto-denied. Uncertain cases escalate to a manual prompt. |
| **smart** (default) | Use an auxiliary LLM to assess risk. Low-risk commands (e.g., `python -c "print('hello')"`) are auto-approved for that command only. Genuinely dangerous commands are auto-denied. Uncertain cases escalate to a manual prompt. |
| **manual** | Always prompt the user for approval on dangerous commands. |
| **off** | Disable all approval checks — equivalent to running with `--yolo`. All commands execute without prompts. |
:::warning

View file

@ -492,10 +492,10 @@ hermes config set privacy.redact_pii false # disable (default)
### Command approval prompts
By default (`approvals.mode: manual`), Hermes prompts the user before running shell commands flagged as destructive (`rm -rf`, `git reset --hard`, etc.). The modes are:
By default (`approvals.mode: smart`), Hermes asks an auxiliary LLM to assess shell commands flagged as destructive (`rm -rf`, `git reset --hard`, etc.). The modes are:
- `manual` — always prompt (default)
- `smart` — use an auxiliary LLM to auto-approve low-risk commands, prompt on high-risk
- `smart` — auto-approve a low-risk command once, deny high-risk commands, and prompt when uncertain (default)
- `manual` — always prompt
- `off` — skip all approval prompts (equivalent to `--yolo`)
```bash

View file

@ -1583,13 +1583,13 @@ security:
```yaml
approvals:
mode: manual # manual | smart | off
mode: smart # smart | manual | off
```
| 模式 | 行为 |
|------|----------|
| `manual`(默认) | 在执行任何被标记的命令之前提示用户。在 CLI 中显示交互式审批对话框。在消息中排队待处理的审批请求。 |
| `smart` | 使用辅助 LLM 评估被标记的命令是否真正危险。低风险命令以会话级持久性自动批准。真正有风险的命令升级给用户。 |
| `smart`(默认) | 使用辅助 LLM 评估被标记的命令是否真正危险。低风险命令仅对当前命令自动批准,真正危险的命令自动拒绝,不确定的情况升级给用户。 |
| `manual` | 在执行任何被标记的命令之前提示用户。在 CLI 中显示交互式审批对话框。在消息中排队待处理的审批请求。 |
| `off` | 跳过所有审批检查。等同于 `HERMES_YOLO_MODE=true`。**谨慎使用。** |
智能模式对于减少审批疲劳特别有用 —— 它让 agent 在安全操作上更自主地工作,同时仍然捕获真正破坏性的命令。

View file

@ -30,14 +30,14 @@ Hermes Agent 采用纵深防御安全模型。本页涵盖所有安全边界—
```yaml
approvals:
mode: manual # manual | smart | off
mode: smart # smart | manual | off
timeout: 60 # 等待用户响应的秒数默认60
```
| 模式 | 行为 |
|------|----------|
| **manual**(默认) | 始终提示用户审批危险命令 |
| **smart** | 使用辅助 LLM 评估风险。低风险命令(如 `python -c "print('hello')"` )自动批准,真正危险的命令自动拒绝,不确定的情况升级为手动提示。 |
| **smart**(默认) | 使用辅助 LLM 评估风险。低风险命令(如 `python -c "print('hello')"`)仅对当前命令自动批准,真正危险的命令自动拒绝,不确定的情况升级为手动提示。 |
| **manual** | 始终提示用户审批危险命令。 |
| **off** | 禁用所有审批检查——等同于使用 `--yolo` 运行。所有命令无需提示即可执行。 |
:::warning

View file

@ -481,10 +481,10 @@ hermes config set privacy.redact_pii false # 禁用(默认)
### 命令审批提示
默认情况下(`approvals.mode: manual`Hermes 在运行被标记为破坏性的 shell 命令(`rm -rf``git reset --hard` 等)之前会提示用户。模式如下:
默认情况下(`approvals.mode: smart`Hermes 会让辅助 LLM 评估被标记为破坏性的 shell 命令(`rm -rf``git reset --hard` 等)。模式如下:
- `manual` — 始终提示(默认)
- `smart` — 使用辅助 LLM 自动批准低风险命令,对高风险命令提示
- `smart` — 低风险命令仅批准一次,高风险命令拒绝,不确定时提示(默认)
- `manual` — 始终提示
- `off` — 跳过所有审批提示(等同于 `--yolo`
```bash