安全深入——权限与隔离
Hermes Agent 官方教程第29篇:权限与隔离
安全深入——权限与隔离
聊到 Hermes Agent 的安全,很多小伙伴第一反应是“不就是装个 Docker 吗?”——其实远不止。官方文档里写得很明白,这是一套 纵深防御(defense-in-depth) 模型,整整八层防线,从“谁能跟你说话”一路管到“容器里能不能写文件”。今天咱们就一层层拆开看,重点聊聊权限审批和隔离这两块硬骨头。
八层防线,层层设卡
简单过一遍这八层,你就知道 Hermes 有多“惜命”了:
- 用户授权——谁能跟 Agent 对话(白名单、DM 配对)
- 危险命令审批——破坏性操作必须有人类点头
- 文件写入安全——
write_file/patch的黑名单和可选沙箱 - 容器隔离——Docker/Singularity/Modal 加固配置
- MCP 凭据过滤——子进程环境变量隔离
- 上下文文件扫描——检测项目文件里的提示注入
- 跨会话隔离——会话之间互不可见,cron 存储路径防目录穿越
- 输入净化——终端工具的工作目录参数做白名单校验,防 shell 注入
每一层单独看可能都有漏洞,但叠在一起,攻击者想穿透就得“过五关斩六将”。
危险命令审批:三种模式随你挑
核心配置在 ~/.hermes/config.yaml 里,approvals.mode 有三个档位:
approvals:
mode: smart # smart | manual | off
timeout: 300 # 等用户回复的秒数
cron_mode: deny # cron 任务撞上危险命令咋办
single_query_mode: deny # 单次查询(-q)会话撞上咋办
unattended_mode: deny # webhook/API 等无人值守会话撞上咋办
mcp_reload_confirm: true # /reload-mcp 前先问一声
destructive_slash_confirm: true # /clear、/new 等破坏性命令先确认
- smart(默认):让一个辅助 LLM 当“风险评估员”。
python -c "print('hello')"这种低风险命令直接放行;真危险的自动拒绝;拿不准的才弹窗问你。 - manual:所有危险命令一律人工审批,一个都不放过。
- off:全部跳过,等于裸奔。文档里特意加了警告——只建议在 CI/CD、容器这类可信环境里用。
YOLO 模式:作死开关,但有底线
YOLO 模式会绕过所有危险命令审批,激活方式有三种:
- 启动时加
hermes --yolo或hermes chat --yolo - 会话里敲
/yolo切换开关 - 设置环境变量
HERMES_YOLO_MODE=1
注意 /yolo 是开关,敲一次开,再敲一次关。开启后界面会有两个明显的视觉提醒:会话开头的红色横幅 ⚠ YOLO mode — all approval prompts bypassed,以及状态栏里的 ⚠ YOLO 标记,生怕你忘了自己在作死。
但有个底线:硬性黑名单(hardline blocklist)永远拦着,YOLO 也绕不过去。也就是说,再怎么放飞,真正要命的命令还是会被摁住。
审批超时与无头模式
几个值得注意的点:
timeout默认 300 秒:审批请求发出后,用户 5 分钟没回应就超时。超时不算拒绝,也不会被“记仇”——想继续干,重新发条消息让它再跑一次就行,会弹一张新的审批卡,而“批准一次”只对那一次调用有效。cron_mode、single_query_mode和unattended_mode:cron 任务、-q单次查询、以及 webhook / msgraph_webhook / api_server 这类无人值守平台上的会话,都是“没人能回答审批”的场景,默认deny——撞上危险命令直接拦下(不用干等整个超时),Agent 得另想办法;也可以设成approve全自动放行。destructive_slash_confirm:/clear、/new、/reset、/undo这些毁会话的命令,现在会弹三选项对话框(批准一次 / 总是批准 / 取消)。在 Telegram、Discord、Slack 上走原生按钮,其他地方文字回退。TUI 里也认这个设置,HERMES_TUI_NO_CONFIRM=1可以强制跳过。
受监管网关的生命周期限制
终端工具还有一道独立且不可覆盖的护栏:禁止从网关自己的受监管进程内部去停止或重启网关。因为自我重启可能在工具执行完成前就把它干掉,进而引发“监管器 / 自动恢复”死循环。用户审批、YOLO 模式、force=True 都不能绕过这道护栏。
这道护栏还会拦下针对网关所跑解释器镜像的“杀进程”命令——taskkill /F /IM python.exe、Stop-Process -Name python、pkill -9 python3、killall python、pkill -f python,以及 pgrep python | xargs kill 这类按名字推导出来的杀法。原因很简单:受监管的网关本身就是个 python 进程,这么一杀连 Agent 自己这一轮都没了。只针对 Agent 自己拥有的进程的杀法可以放行:后台任务的 proc_* id(process(action="kill", …))或明确的 PID(taskkill /F /PID <pid>、kill <pid>)。其他镜像名(比如 taskkill /F /IM notepad.exe)不受影响。这道护栏在所有生成的启动器下都生效——systemd unit、launchd plist、s6 run script 和 Windows 计划任务,靠它们导出的 HERMES_SUPERVISED_CHILD 标记来识别。
在 macOS 上,实际执行的 launchctl submit 和 launchctl bootstrap 命令会被限制,不管 job label 是什么。这是一条保守的注册限制,目的是拦住那些用中性 label 的间接重启助手,而不是去检查目标 plist。它也会拒绝那些 RunAtLoad=false 且没有 KeepAlive 键的独立计划任务;被拒绝并不意味着该任务一定用了 KeepAlive 或控制了 Hermes。
如果确实需要维护 LaunchAgent,请在网关之外单独开一个 shell 操作。某些独立的 load/unload 命令目前能通过基于 label 的检查,但这并不是经过目标验证的豁免,也不是绕过 bootstrap 拒绝的受支持方式。只读的 launchctl print 不属于生命周期操作。外部维护之后,要把磁盘上的 plist 和已加载的 job 区分开:先校验 plist,再读回已加载的计划,然后才能报告“已激活”。
工具拒绝意味着该命令没有通过那次工具调用执行;助手自己决定不发这个调用,是另一回事(模型决策),换个模型也不会改变终端护栏的策略。
用户自定义拒绝规则(approvals.deny)
硬性黑名单是代码里写死的,approvals.deny 则是它的“用户可编辑版”:一串 glob 模式,匹配到的终端命令会被无条件拦截——在 --yolo、/yolo、approvals.mode: off 生效之前就拦下。想“除了这几件事,其他随便干”的话,就用它。
approvals:
deny:
- "git push --force*"
- "*curl*|*sh*"
- "dd if=* of=/dev/*"
几个细节:
- 模式是 fnmatch glob(
*、?、[...]),大小写不敏感,匹配整条命令文本,也匹配单个可执行命令候选。git push --force*能匹配git push --force origin main,但匹配不到git push origin main。 - 匹配跑在危险模式检测器用的同一套“规范化 / 去混淆”命令变体上,所以
git pu""sh --force这种简单引号把戏躲不过去。 - 可执行候选既保留字面路径,也匹配它的 basename:
sudo *覆盖/usr/bin/sudo -n id和./sudo -n id;而/usr/bin/sudo *这种路径专属规则不会变成对所有叫sudo的二进制都生效。 - 引号感知的解析能揪出赋值、前置重定向、
;、&&、||、管道、分组、命令替换,以及普通if/then/else/do转换之后的命令。支持的启动器包括sudo、env、command、exec、nohup、setsid、time、nice、timeout、stdbuf、ionice、chrt、taskset、chroot。已知的选项操作数会被跳过;command -v/-V查询不算执行。shell-c载荷会被递归检查。env -S/--split-string里的字面“可执行文件 + 参数”字符串按 GNU 引号和转义规则处理(包括\_词边界和\c终止),其余命令参数追加在后面;这些参数里的 shell 标点保持为数据,除非真的被某个 shell-c消费。env -a/--argv0的值是参数,不是可执行文件名。shell 和 GNU split-string 的注释不会引入可执行候选。 - 在额外的可执行候选中,词与词之间的空白会被折叠,但引号内的参数内容和参数路径会保留。所以像
git status这样的精确规则,也能匹配env git\tstatus; echo done(\t代表制表符)。而echo 'sudo -n id'这种引号里的“提及”不会被提升为命令。已有的整条输入 glob(比如*sudo*)仍然会有意匹配这类提及。 - YAML 引号:模式一定要加引号。裸的开头
*会被 YAML 当成别名解析失败,{、!、:也各有含义,单引号对 shell 风格内容最安全。 - 拒绝规则对所有终端后端都生效,包括隔离容器,且先于任何后端专属的审批捷径。被拦下的命令会给 Agent 返回一个 BLOCKED 错误,告诉它别重试、别换说法,什么都不会执行。
- 和审批配置的其他部分一样,改完立即生效(配置缓存按 mtime 判断),不用重启会话。
小结
安全这玩意儿,从来不是单点防御,而是层层设防。Hermes 的八层模型把“人、机、环境”都考虑到了:人负责审批关键操作,机器负责扫描和隔离,环境负责容器和路径加固。对于普通用户,保持默认的 smart 模式就够用;真要开 YOLO,记得自己心里有点数。
📖 官方文档
本文根据 Hermes Agent 官方文档编写,原文见:官方文档 › user-guide/security