Hermes 现在会把远程沙箱里生成的文件送到你手上


想象一个周五晚上:你把 Hermes 的“手”放进了一台远程沙箱——也许是 Modal 的云端环境,也许是公司的一台 SSH 开发机——图的是隔离干净、不碰你笔记本上的文件。Agent 干完活,在沙箱里生成了一张图表、一份报告、一张网页截图,兴冲冲地在回复里附上文件路径。结果聊天窗口里什么都没有,日志里只有一行:“Skipping unsafe MEDIA directive path”。文件确实生成了,就是到不了你手上。

文件为什么会“凭空消失”

要理解这个问题,先得知道 Hermes 是怎么把文件递给你的。Agent 在回复里写一个 MEDIA:<路径> 标签,网关(gateway)看到后把对应文件取出来,作为图片或附件发到你的聊天里。过去,这个路径校验只认网关宿主机上的文件——这是刻意的安全设计:媒体投递不能成为读取任意系统文件的通道,所以 ~/.ssh、系统目录、Hermes 自己的凭据存储统统进了 denylist,一律拒绝。

问题就出在“只认宿主机”上。当你的终端后端是 ssh、Modal、Daytona、Singularity 或 Vercel 沙箱时,agent 的终端命令跑在另一台机器/另一个文件系统上,MEDIA: 指向的文件根本不在网关宿主机。于是校验失败,附件被当成“不安全的路径”直接丢弃——不是文件没生成,而是网关够不着它。

修复:把文件从沙箱里“取回来”

2026 年 9 月 5 日合并的 PR #103600(对应 issue #466 的出站半边)改变了这一切。思路很直接:宿主机路径校验失败时,如果当前会话确实跑在一个远程沙箱里,网关就通过执行通道把文件拉出来,再走一遍正常投递流程。

具体分三层:

  • 传输层tools/environments/base.py):给所有环境后端加了统一的 fetch_file / fetch_realpath——把文件 base64 编码后经执行通道传回,大小在沙箱内部就卡死(用 head -c max+1 截断,和 tools.image_source 同一招,防止 /dev/zero 这类无限流打爆宿主机内存),并用标记围栏隔离登录 shell 的杂音输出。
  • 触发层(新增 gateway/media_fetch.py,约 120 行):只在“远程后端激活 宿主机校验已失败”时触发——本地后端永不走这条路径。
  • 挂钩层gateway/platforms/base.py::_validated_delivery_path):三行代码,是所有 MEDIA 过滤入口共用的唯一咽喉,一处修复处处生效。

安全边界:不是把沙箱当后门

远程取文件最怕变成 denylist 的绕过通道。所以取回前有两道筛查:路径先按宿主机同样的 denylist 过滤(系统前缀、~/.ssh 一类目录、Hermes 凭据存储),readlink -f 解析真实路径后再筛一遍——符号链接指向凭据文件?直接拒绝,一字节都不会传。通过筛查的文件被复制进 cache/documents/(本身就在投递白名单根目录里),之后按普通宿主文件同等校验投递。上限 50MB,与各平台上传上限一致。

想保持旧行为的用户也有开关:HERMES_MEDIA_DELIVERY_STRICT 严格模式下,远程文件依然一概不取——取回副本会落在白名单根目录,可能绕过严格模式依赖的 recency 门禁,所以该模式下宁可保持原样。

哪些场景受益

远程终端后端(ssh / Modal / Daytona / Singularity / Vercel 沙箱)里由 agent 产出、并以 MEDIA: 标签引用的文件,现在都能正常送达:沙箱里跑的爬虫截图、数据分析脚本生成的图表、长任务结束后写的报告文件——凡是你让 agent “存下来发给我”的东西。本地后端行为不变,原本就无需取回。

值得说明的边界:能上报远程主目录(_remote_home)的后端(如 ssh、Daytona、Vercel)获得完整支持;Modal、Singularity 及无宿主挂载的 Docker 因无法确认远程 home,仍走保守的“任意组件”denylist——位于 /root 之类系统前缀下的文件默认仍不取回,除非环境显式设置 _remote_home 选择加入。

现在还缺什么

这次只完成了 #466 的“出站”一半。PR 明确未包含:独立的 send_file 核心工具(现有 MEDIA: 标签就是接口)、各后端的原生传输通道,以及“入站附件→沙箱注入”(把用户发的文件送进沙箱,仍在 #466 上挂着)。前两者按脚印阶梯(footprint ladder)逐步推进,不急于一次做完。

怎么用上

无需新命令、新配置——这是网关行为修复,升级后自动生效。目前改动已合入 main(2026-09-05),尚未进入任何正式版本 tag;想立刻体验可用 dev/main 安装,等下一个发布版本则直接 hermes update 即可。升级后可以做个快速验证:在远程沙箱里让 agent 生成一张图片并“发给你”,再故意放一个指向 ~/.ssh 的符号链接试试——前者应该正常送达,后者依旧被拒,两道筛查都在干活。

关于沙箱与后端,可延伸阅读我们此前的共享 Docker 容器指南可插拔终端后端指南;版本发布节奏见发版记录