From 62a76bd3d5a658b84b3dafff6233f13e2522b95e Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Sun, 12 Jul 2026 00:25:55 -0700 Subject: [PATCH] feat: make smart approvals the default (#62661) --- hermes_cli/config.py | 6 +-- .../hermes-agent/SKILL.md | 6 +-- tests/tools/test_approval.py | 38 ++++++++++++++++++- tools/approval.py | 6 +-- website/docs/user-guide/configuration.md | 6 +-- website/docs/user-guide/security.md | 8 ++-- .../autonomous-ai-agents-hermes-agent.md | 6 +-- .../current/user-guide/configuration.md | 6 +-- .../current/user-guide/security.md | 6 +-- .../autonomous-ai-agents-hermes-agent.md | 6 +-- 10 files changed, 65 insertions(+), 29 deletions(-) diff --git a/hermes_cli/config.py b/hermes_cli/config.py index 9b9359888fa..46839ca18f7 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -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 diff --git a/skills/autonomous-ai-agents/hermes-agent/SKILL.md b/skills/autonomous-ai-agents/hermes-agent/SKILL.md index de6f398df6d..8413e4cf208 100644 --- a/skills/autonomous-ai-agents/hermes-agent/SKILL.md +++ b/skills/autonomous-ai-agents/hermes-agent/SKILL.md @@ -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 diff --git a/tests/tools/test_approval.py b/tests/tools/test_approval.py index e364dcd3be0..434fa9af2cb 100644 --- a/tests/tools/test_approval.py +++ b/tests/tools/test_approval.py @@ -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)) diff --git a/tools/approval.py b/tools/approval.py index 05f2eb523cc..365b63da521 100644 --- a/tools/approval.py +++ b/tools/approval.py @@ -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, diff --git a/website/docs/user-guide/configuration.md b/website/docs/user-guide/configuration.md index 269b7cff44c..d9a4353e23e 100644 --- a/website/docs/user-guide/configuration.md +++ b/website/docs/user-guide/configuration.md @@ -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. diff --git a/website/docs/user-guide/security.md b/website/docs/user-guide/security.md index 60eec972e28..71d1b131d2e 100644 --- a/website/docs/user-guide/security.md +++ b/website/docs/user-guide/security.md @@ -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 diff --git a/website/docs/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-hermes-agent.md b/website/docs/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-hermes-agent.md index caa66b64e7a..7c9eec273f5 100644 --- a/website/docs/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-hermes-agent.md +++ b/website/docs/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-hermes-agent.md @@ -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 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/configuration.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/configuration.md index c3ea44ef957..c2474028849 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/configuration.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/configuration.md @@ -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 在安全操作上更自主地工作,同时仍然捕获真正破坏性的命令。 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/security.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/security.md index 911b8624016..bde9a38431a 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/security.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/security.md @@ -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 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-hermes-agent.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-hermes-agent.md index 196fdda0006..c1d8a9436a7 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-hermes-agent.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-hermes-agent.md @@ -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