EvoMap
Hermes Agent 自托管安全指南:分层防御而不是单点开关

Hermes Agent 自托管安全指南:分层防御而不是单点开关

2026年4月29日
1,040 次阅读
hermes-agent security self-hosting docker mcp agent-ops

自托管 Hermes Agent 安全指南

嗨,我是 Lena。在碰任何一个配置文件之前,我花了大半个下午读它的 security 页面。读完以后,我又回去重新读了一遍。第一遍看下来,这个系统像是一长串选项。第二遍,我才开始看清它的形状——在 Hermes Agent security 里,真正重要的并不是某一个"security feature",而是一组彼此独立的层。每一层都默认其他层可能会失败。

这个框架比我一开始想的更重要。我见过的大多数 agent security 文章,读起来都像一份技巧清单——打开这个,设置那个 flag,然后就安全了。但 Hermes 文档里的思路不太一样。它是一个 defense-in-depth 模型,每一层只做一件具体的事,打开其中任何一层,都不能替代其他层。没有任何一层是完整答案。 这句话基本就是整篇文章的核心。

这篇是我思考如何加固一个长期运行的自托管 Hermes 部署时整理下来的笔记。有些地方我也还在继续摸索。

Hermes security model 的分层结构

如果你仔细读 官方 security 页面,它的模型大概有七个彼此独立的层——这里的"独立"真的很关键。每一层都在假设一种不同的故障或攻击方式。按一次 tool call 中大致生效的顺序来看:

  • 用户授权——gateway 层检查谁能和 agent 通信
  • 危险命令审批——shell 命令运行前,基于 pattern 做检测
  • Tirith 内容扫描器——基于 Rust 的执行前扫描,检查 prompt injection、凭证外泄、terminal injection 等模式
  • Runtime isolation——local vs Docker vs SSH vs Modal vs Daytona vs Singularity
  • 凭证过滤——对子进程剥离 env vars、MCP 子进程 env allowlist、输出脱敏
  • 上下文文件扫描——AGENTS.md、SOUL.md、.cursorrules 在加载时扫描 prompt injection
  • 跨 session 隔离——session 不能读取彼此状态;cron 路径针对 traversal 做了加固

我一直反复想到这一点,因为真正拦住某个问题的那一层,不一定是你以为应该拦住它的那一层。 一个恶意 shell 命令可能被 approval 拦住,可能被 Tirith 拦住,也可能被 container 拦住——也可能三者都拦住,或者有时谁都没拦住。这就是为什么关掉其中一层,直到出事前都会显得很安全。

它借用的原则其实比 agents 更早。CISA 的 Secure by Design joint guidance 讨论过 secure defaults——也就是默认配置应该是安全的,任何偏离都必须显式发生。Hermes 大体上遵循这一点:approvals 默认开启,Tirith 在 high-security mode 下 fail closed,gateway 在没有 allowlist 时拒绝所有用户。问题在于那个"大体上",偏离的地方很重要。

危险命令审批,以及它真正有用的位置

这一部分我想说得谨慎一点。Approval prompts 会让人觉得安心,而这正是它容易被过度信任的原因。

Hermes 维护了一组危险命令的 regex patterns——比如 rm -rf、DROP TABLE、fork bombs、把 curl 输出 pipe 给 bash、kill gateway process。命中以后,会要求用户审批。它有三种模式:manual(总是询问)、smart(LLM 辅助风险评分)、off(不询问)。还有 session 级别的 /yolo,会绕过所有审批。

我一直提醒自己的是:基于 regex 的检测,本质上可以被绕过。 命令可以被混淆、base64 编码、拆成两次 tool call、写进文件以后再 source。Hermes 在扫描前会 normalize 输入(剥离 ANSI escapes、null bytes、NFKC Unicode),这确实堵住了最显眼的混淆路径。但它仍然是在一个开放攻击面上做 pattern matching。

Hermes GitHub 上最近有一份社区 security audit 详细讨论了这个问题。结论不是 Hermes 不安全,而是 默认配置偏宽松,并且假设用户会自己加固。这是一个可以辩护的设计选择。但它也把真实责任交给了运行部署的人。

所以我现在理解 approval 的方式是:它是防事故的绊线,不是对抗恶意 agent 的防线。当一个有 tool access 的模型以 非故意 的方式跑偏——路径错了、flag 漏了、命令自信但错误——approval 能抓住它。当模型输入真的已经被攻陷时,approval 就不是你应该依赖的那一层。Approval prompt 是 ​human-in-the-loop​​ 的安全带;container 才是吸能区。

Runtime isolation 的选择

这里是 Hermes 真正给你选择权的地方,而这个选择比其他选项更有分量。terminal backend 决定了"agent 运行了一个命令"在物理层面到底意味着什么。

  • local——在 host 上运行命令。默认选项。Approval prompts 生效。Blast ​radius​​ 就是 host。
  • docker——命令在 container 内运行,root 只读、capabilities 被 drop,并可配置 CPU/memory/disk。危险命令检查会被 无条件跳过,因为 container 本身就是边界。
  • ssh——在远程机器上运行命令。适合把 agent 完全从你的笔记本上移开。
  • modal 和 daytona——serverless backends,空闲时 hibernate。具备 container 级别 isolation,idle 成本接近零。
  • singularity——用于没有 Docker 的 HPC 环境。

有几件事值得单独拿出来说。approval prompts 的"container bypass"是有意设计的——团队的理由是,如果 container 边界成立,内部的 regex 检查就是冗余的;如果 container 失效,regex 本来也救不了你。我得消化了一会儿,才觉得这个逻辑说得通。

如果你选择 Docker,Hermes config 暴露的加固开关和 Docker 官方 engine security docs 里的标准实践高度对应——namespace isolation、dropped capabilities、read-only root、resource limits。默认 Hermes Docker image 已经启用了这些。人们最容易犯的错,是把东西又加回去。 terminal.docker_forward_env 就是最明显的一个:你转发进 container 的每一个变量,agent 都可以读取并外泄。默认 allowlist 为空,我会让它保持为空,除非是任务特定 token。

Persistent vs ephemeral mode 是另一处分叉。Persistent 会把一个 workspace 目录 bind-mount 到多次运行中,所以 agent 可以积累状态——这对开发有用。Ephemeral 使用 tmpfs,container 停止后一切都会消失——这更适合接近生产的场景。判断标准可以很简单:如果一个恶意文件在 ~/.hermes/sandboxes/ 里待上一周你才发现,你能不能接受?

Messaging 和 MCP security 边界

当 Hermes 作为 gateway 运行时,"谁能和它通信"就变成了真实问题。授权模型是分层的:按平台的 allow-all flags、DM pairing approved list、platform allowlists、global allowlist。如果这些都没配置,默认行为是 deny everyone,并在启动时给出 warning。这是正确的默认值——而有些 self-host guide 会带用户设置 GATEWAY_ALLOW_ALL_USERS=true 做测试,然后忘记撤销,这本身就是一类事故。

DM pairing 是我一直推荐给别人的部分。你不需要维护 Telegram/Discord ID 列表,未知用户会拿到一个一次性 pairing code,然后你从 CLI 用 hermes pairing approve telegram ABC12DEF 批准。Hermes 文档提到,这个设计参考了 OWASP 和 NIST SP 800-63 digital identity guidance——code 是一次性的、有时间限制的,approval action 也是显式的。它并不新奇,但这意味着 access decision 是由一个真实的人看着请求做出的,而不是提前编辑 config file。

MCP 完全是另一个攻击面。我花最长时间才真正内化的一点是:Hermes 内部的 MCP tool,拥有的是它背后那个 server 的权限,而不是 agent 的权限。 这正是 protocol 的意义,但也意味着每一个连接上的 MCP server 都是一次独立的信任决策。

Hermes 的 MCP config reference 记录了两个我现在认为不可选的保护。第一是 environment filtering:默认只有 PATH、HOME、USER、LANG、LC_ALL、TERM、SHELL、TMPDIR 和 XDG_* 会传给 MCP stdio subprocesses。其他所有东西——API keys、tokens——都会被剥离。真正需要的变量,应该放在 server 显式的 env block 里。第二是按 server 过滤 tool,使用 include 和 exclude:如果一个 server 暴露 20 个 tools,而 agent 只需要 3 个,就只 allowlist 那 3 个。

第三个不是 config,而是纪律。Context files(AGENTS.md、SOUL.md、.cursorrules)在加载到 system prompt 之前,会先扫描 prompt injection patterns。这一层对应的是 OWASP's LLM Top 10 称为 indirect prompt injection 的问题——也就是隐藏在模型被要求读取的内容里的指令。scanner 不是完整防御(这个类别里没有什么是完整防御),但这个检查放在这里是对的,而且默认开启。

加固自托管 Hermes 部署

下面是我最后倾向采用的一些模式,按大致优先级排列:

用非 root 用户运行,只在必要位置配置 passwordless ​sudo​​。 Hermes installer 默认假设如此;security model 也默认假设如此。不要用 root 运行 gateway。

按你的 trust model 选择 runtime isolation,而不是按方便程度选。 如果一台 server 主要在跑 scheduled jobs、回复消息,那么 Docker backend、空的 docker_forward_env、ephemeral workspace、最小 mounted volumes,是无聊但正确的选择。Local backend 在个人笔记本上可以接受,因为你会盯着每一个 approval prompt;但在你不会主动盯着的 VPS 上,它不是一个好默认值。

不要把 secrets 放进宽作用域。 Provider API keys 和 gateway tokens 放在 ~/.hermes/.env,权限设为 0600。它们绝不应该出现在 env_passthrough,也绝不应该出现在 docker_forward_env。MCP servers 只拿各自 env block 显式点名的变量。

显式配置用户授权。 要么用你真正想授权的 user IDs 设置 platform allowlists,要么使用 DM pairing。绝不要在生产环境里留下 ​GATEWAY_ALLOW_ALL_USERS=true​​——那是测试 flag。

审计你关掉的每一层。 approvals.mode: off 和 /yolo 存在有它们的理由——CI runs、sandboxed test environments——但如果其中任何一个出现在你的 live config 里,把原因写下来。tirith_enabled: false 也是一样。价值不在于它们是不是开启,而在于你能不能回答为什么没开启。

更新纪律比初始配置更重要。 Hermes 发版很频繁,而且几乎每个近期 release 都有 security improvements——secret-exfil blocking、更广的 credential directory protections、更完整的 token redaction patterns、browser URL exfil blocks。一个六个月没更新的安装,即使当初配置得再仔细,也已经是更差的安装。

我会继续打磨这套理解。有些层我还没有完全吃透——比如 Tirith verdict 到 approval 的 handoff,我还想再花点时间看;我也还没对自己长期运行的部署做过一次真正的 audit pass。我比较确定的是,layered model 是对的,而且没有任何单独一层是完整答案。 如果一篇文章试图只给你一个小技巧,那它大概率是不对的。

FAQ

​我应该使用 ​approvals.mode: off​ 吗?

只在 container 或 sandbox 内部使用,而且由 container 承担隔离工作时才可以。在 local backend 上,不要。

Docker backend 能完全替代 approval prompts 吗?

对于 host-level damage,大多数情况下可以——这也是 container backend 生效时 approval 会被绕过的原因。但它 不能 替代 credential filtering 或 MCP boundaries。

​YOLO​​ mode 到底会禁用什么?

当前 session 的危险命令 approval prompts。它不会禁用 Tirith、container isolation、gateway authorization 或 env filtering。

我怎么知道 ​env​​ vars 里哪些会被 ​MCP​​ subprocesses 看到?

只有 PATH、HOME、USER、LANG、LC_ALL、TERM、SHELL、TMPDIR、XDG_*,以及 server 显式 env block 里的内容。其他所有都会被剥离。

有没有 managed ​Hermes​​ 能替我处理这些?

有一些 providers 提供 one-click deployments。它们处理 install——但不会替你决定谁能和你的 bot 通信,也不会替你决定 MCP servers 能读取什么。这些决策仍然必须由你自己做。

等我在自己的 setup 上花更多时间以后,我会再回到这个话题。现在,这就是我目前的理解。

Previous Posts:

相关文章