大家好,我是孟健。
今天这个坑很小:文件生成了,Telegram 收不到。

01 小坑:文件生成了,手机收不到
场景很具体。
Agent 生成了一张配图,路径回来了,MEDIA:/tmp/article-cover.png。消息发出去了,Telegram 显示发送成功,打开手机------只有文字,附件不见了。
第一反应是 bot 挂了。重启,没用。然后去查 Telegram bot API 的状态,正常。再想会不会是文件大小超限,检查了一遍,几百 KB,格式 PNG,都没问题。换了一次代理,还是没有。又想是不是发送时机问题,写了个脚本反复重试,依然空手而归。
就这样折腾了半小时,每一步看起来都"成功了",但手机那边始终没有附件。
最后去翻 gateway 日志,才找到真正的原因。日志里有一条很短的记录:文件路径未通过安全校验,上传跳过。消息本身发出去了,但文件根本没走到上传这一步。
回头翻提交记录,看到 2026-05-22 有一条 Fix unsafe gateway media path delivery,改动涉及 gateway/platforms/base.py、tools/send_message_tool.py、cron/scheduler.py 等文件。不是 bug,是主动升级。
这是 Hermes 安全模型的一次有意为之的收紧。升级之前,Agent 说"发这个文件",gateway 就发。升级之后,多了一道闸门,Agent 说的路径得过了校验才能上传。
看起来是附件消失,实际上是安全模型在替你挡了一枪。
02 为什么要拦:MEDIA 路径本质是不可信输入
理解这道闸门,先想清楚一件事:AI 生成的文件路径,其实就是一段文本。
Agent 回给 gateway 的内容是 MEDIA:/tmp/article-cover.png,这个字符串是 LLM 生成的。LLM 在生成这个字符串的时候,没有去碰文件系统,没有打开过这个文件,没有验证它存在不存在,更没有检查它该不该发出去。它只是根据上下文生成了一串看起来合理的路径文本。
这就是问题所在。LLM 能生成 MEDIA:/tmp/article-cover.png,也完全可以生成某个敏感配置文件、凭据文件,或者服务器上任何一个存在的本地路径。不需要恶意攻击,只需要上下文稍微偏一点,或者有人通过 prompt injection 给 Agent 塞了一段特定指令。
如果 gateway 的逻辑是"看到 MEDIA 标记就上传",这个链路就等于把服务器文件系统的读权限,通过 Telegram bot,间接开放给了任何能影响 Agent 输出的人。攻击面很明显,不是假设风险。
Hermes 的安全文档里强调 defense-in-depth,从命令审批、用户授权,到凭据处理、Web 安全、SSRF 防护、prompt injection 拦截,是多层设计,不依赖单一防线。文件上传这里,是在"Agent 输出"和"实际交付"之间加了一环独立校验。
这个设计原则很清楚:Agent 的输出跟用户输入一样,都是不可信文本,必须在边界上做验证。
不信任用户输入,是 Web 开发的基本常识。把同样的不信任延伸到 Agent 输出,是 AI 系统进入生产后必须面对的现实。

03 Hermes 现在怎么判断一个文件能不能发
核心逻辑在 gateway/platforms/base.py 的 validate_media_delivery_path(path) 函数里,五步校验,顺序不能乱,每一步都有对应的防护目标。
第一步,绝对路径检查。 传进来的路径必须是绝对路径,相对路径直接返回 None,不做后续处理。相对路径依赖调用时的工作目录,不同进程、不同时机,解析结果可能完全不同,不具备可预期性。
第二步,文件存在检查。 路径合法不等于文件在。路径指向的文件必须在磁盘上真实存在,否则拒绝。这一步过滤掉了大量"路径是 LLM 编出来的"情况,Agent 有时候会幻觉出一个听起来合理但根本不存在的路径。
第三步,普通文件检查。 必须是普通文件,目录、socket、字符设备、块设备一概不行。防的是通过传入目录路径来绕过、或者上传设备文件触发意外行为。
第四步,symlink resolve。 如果路径是软链接,先解析到真实路径,然后再做后续校验。这步非常关键,防的是一种经典绕过手法:把软链接本身放在允许目录下,但链接指向目录外的敏感文件。不 resolve 直接检查软链接路径,就会放行。
第五步,安全根目录检查。 解析后的真实路径,必须落在 Hermes 自管理的 media cache 目录下,或者在 HERMES_MEDIA_ALLOW_DIRS 环境变量显式声明的目录下,缺一不可。五步里任何一步不过,函数返回 None,上传终止,消息文本正常发出,附件静默跳过。
我做过验证:/tmp/article-test-media.txt 传进去返回 None,直接被拦;media_outbox 下的文件,通过全部校验,可以正常上传。

/tmp 是最常见的"生成文件随手往这放"的场景,但它不在安全根目录里,也确实不该被默认允许。/tmp 是共享临时目录,任何进程都能往里写,把它加进白名单,等于把 gateway 的上传权限开给了整台机器上所有能写 /tmp 的进程。
五步校验的逻辑是:你要发的文件,必须是你可控范围内的文件。
04 我最后怎么修
拿到原因之后,面前有两个选项。
一是把整个 workspace、/tmp 或者用户主目录加进 HERMES_MEDIA_ALLOW_DIRS。一行环境变量的事,能立刻解决问题,但等于把刚加的闸门拆了,白升级。安全升级不是摆设,绕过它不是"快速修复",是主动降级。
一是建一个统一的附件出口,在不破坏安全边界的前提下,给 Agent 一个合法的、有明确边界的操作空间。
我选了第二个。

方案核心:~/.hermes/media_outbox 作为唯一对外附件目录。
具体分四步走:
建 outbox 目录。 ~/.hermes/media_outbox/ 是所有对外附件的唯一出口,含义明确。里面只放要发出去的文件,不往里随意堆其他东西。目录本身的存在,就是"这里的文件都是准备发出去的"这个语义的体现。
写预处理脚本。 ~/.hermes/scripts/media_outbox_prepare.py 负责把原始文件从生成位置复制进 outbox。复制前做三件事:扩展名白名单校验,只允许 png、jpg、pdf、mp4 等常见格式,其他一律拒绝;文件大小上限校验,防止发出去一个几 GB 的文件把 bot 撑死;文件名脱敏,去掉内部绝对路径信息,生成的目标文件名不带任何内部目录结构。脚本的输出是绝对路径形式的 MEDIA:/.../.hermes/media_outbox/...,Agent 直接拿这个路径发消息,不需要再做任何额外处理。
注入环境变量。 通过 systemd drop-in 文件,给所有 gateway service 注入 HERMES_MEDIA_ALLOW_DIRS=<绝对路径>/.hermes/media_outbox。所有平台的 gateway 自动生效,不需要逐一配置,也不会漏掉某个 service 忘记改。
定时清理。 加了一个 cron 脚本,保留 14 天内的文件,自动清理过期附件。outbox 有专门用途,不能让它随着时间堆成新的垃圾桶,清理是 outbox 设计的一部分,不是事后补丁。
这套方案的整体逻辑:安全根目录的边界不动,但在边界内,给 Agent 划出一个受控的合法操作空间。文件想出去,必须先经过 outbox 这道门,门里有格式校验、大小校验、文件名校验。什么文件能出去,一目了然,审计起来也方便。
安全边界必须是硬的,靠 prompt 说"你不要发敏感文件",是靠自觉,自觉不可靠。
05 这个小坑给 Agent 产品的启发
做 Hermes 到现在,这是第一次正经遇到"Agent 能生成,但系统不允许它交付"的情况。乍一看像 bug,实际上是系统行为正确------它拦住了一个本该被拦住的操作。
过去做 Agent 产品,大部分精力放在能力上:它能不能理解需求,能不能写对代码,能不能生成好内容,能不能在多个工具之间正确协作。这些很重要,是 Agent 有没有用的前提。但进入生产工作流之后,真正难的是另一侧------能做什么和不该做什么的边界,必须是系统层面的约束,不能靠 prompt 来撑。
靠 prompt 控制边界,是让 Agent 自己管自己。这个思路有一个根本性的弱点:Agent 越智能,它"智能地绕开限制"的能力也越强,而且往往不是主动的,是场景和上下文把它推过去的。你 prompt 里写了"不要发敏感文件",但 Agent 在执行某个工具链的中间节点,根本意识不到当前文件是不是"敏感的",因为它没有访问文件内容,只有路径字符串。
系统层面的约束才是稳的。发文件,有 outbox,只有 outbox 里的文件能出去;发消息,有审批,只有审批过的内容能对外;删数据,有确认,人没点确认就不执行。这些约束让 Agent 可控地做事,让系统知道它在做什么,做了什么,做了多少。可观察性和可控性,是 Agent 在生产里长期跑的基础。
对所有在做 Agent 产品、把 Agent 接进实际工作流的人,我的建议是:凡是涉及文件、凭据、发布、支付、删除、外发这几类操作,都要有显式出口 + 安全闸门。显式出口让你知道什么东西会出去,安全闸门让你确认它该不该出去。出口和闸门缺任何一个,都是把管控权悄悄让渡给了 Agent。
这篇讲的是 Telegram 附件,但同样的逻辑适用于所有"Agent 产出要触碰真实世界"的地方。邮件要发出去,文章要推送,代码要部署,金额要扣除------每个环节都是一道门,门上有没有锁,锁是不是系统级别的,决定了整个工作流安不安全。
Agent 越像员工,就越不能让它随手拿办公室里的任何文件。
👋 我是孟健,前腾讯 T11 / 前字节技术 Leader,现在全职做 AI 编程。
🔥 更多 AI 编程实战:
- GitHub:@mengjian-github
- 专栏:AI编程实战
觉得有用?点赞+收藏 就是最大支持 🙏