AI Agent 写入权限陷阱:发布成功但发错账号的 MCP 防护实践
原文:https://dev.to/eugeniya_ivanova_4a58eadc/the-agent-posted-successfully-to-the-wrong-account-3kf3(作者 @eugeniya_ivanova_4a58eadc)
七月时我写过一篇文章,讲的是将 AI agent 接入社交平台需要做些什么:六个 OAuth 流程、三步媒体上传、令牌按各自的私密时间表过期。当时的结论是把这些全部藏到一个工具调用背后,然后不再去看它。
两个月过去了,这部分已经完成。我们的 MCP 服务器现在通过 OAuth 响应,在 /.well-known/oauth-authorization-server 提供了正确的元数据,支持 PKCE 和动态客户端注册,因此连接编辑器不再需要在配置文件中输入密钥。十六个工具,一个端点。管道工程已经就绪。
我错在以为管道工程才是风险所在。
我的工作是把产品交到用户手中,这意味着我以希望其他用户使用的方式来使用它:我让一个 agent 去发布内容,然后回去做自己的事。这样用了几个月后我学到的是:一旦 agent 有了写入权限,故障就不再主动暴露了。
看起来完全正确的标识符
语言模型擅长生成看起来合理的字符串。这就是它的全部技能。让它往 LinkedIn 发帖,它能给 API 一个前缀正确、长度正确、格式正确,但指向错误账号的值。
这个请求没有任何格式错误。没有异常可以捕获。API 被要求做一件具体的事,它做了。
因此,我们的服务器给任何客户端的第一条指令不是描述它能做什么,而是一条规则:先调用 list_connections,逐字复制每个 platformId,绝不自己编造。在工具列表之前,在示例之前,在任何解释产品用途的内容之前。
为一个会自信地即兴发挥的读者写文档,和为一个会感到无聊而跳读的人类写文档,是完全不同的工作。
"明天上午9点"是一个时区问题
API 只接受 UTC 的 ISO 8601 格式,其他一律不收。所以当你说"明天上午9点"时,必须有东西来决定你指的是哪个9点,而这个东西就是模型。
它大多数时候是对的。当它不对时,调用层面不会暴露任何问题。响应是正常的成功,帖子带着一个完全有效的时间戳进入队列,然后你在凌晨4点从帖子本身发现问题。
我现在会在确认信息中回读计划时间。不是因为模型算术不好,而是因为这里的错误答案和正确答案无法区分,直到为时已晚。
现在接受,稍后死亡
Instagram、TikTok 和 YouTube 不会在没有媒体内容的情况下发布。但没有什么能阻止 agent 安排一条纯文本的 Instagram 帖子:它通过验证,进入队列,看起来健康地待了一天,然后在发布时死掉。
进入队列不等于承诺。这种区别只有被坑过才能学到,因为在那之前,故障完全不可见。
没人看的注解
MCP 允许服务器为每个工具标注关于其行为的提示:readOnlyHint、destructiveHint。我们的都填了。十六个工具中有六个被标记为破坏性:删除帖子、删除媒体、移除 LinkedIn 评论或反应。
{
"name": "delete_post",
"annotations": { "readOnlyHint": false, "destructiveHint": true }
}
它们是建议性的。客户端可以完全忽略它们,而且很多客户端确实如此。但添加它们几乎不花成本,而且它们是服务器告诉客户端"这个操作值得弹个确认框"的唯一方式——不需要发明私有协议。如果你运行一个 MCP 服务器且还没填这些注解,这是二十分钟的工作,能让每个行为规范的客户端替你保护用户。
一个用于发布的假平台
我最喜欢的修复方案是最不聪明的那个:
{ "content": "test", "platforms": ["publora-playground"] }
它接受帖子,按真实规则验证,返回正常响应,然后丢弃。没有任何东西到达真实网络。
它存在是因为之前没有诚实的方式来回答"这个连接正常工作吗?"每个真正的端到端测试都涉及在某个人的真实时间线上放一些真实内容,这在凌晨2点是个不错的测试方式,在其他任何时间都是糟糕的。现在整个往返流程无需观众就能测试。
每个向公开位置写入内容的集成都应该有一个这样的东西,但大多数没有。
如果能对七月的自己说
我对产品有偏见,所以这部分不谈产品。
当你给一个 agent 对外发布内容的写入权限时,值得防范的故障不是 500 错误。异常会进入日志,最终有人会读到。危险的是那个成功执行却悄悄做错事的调用:格式正确,目标错误,整条链路没有任何错误。
七月版本的做法是"把复杂性藏到一个工具调用背后"。我仍然认为这是对的。我现在会加上后半句:并且让这个工具调用难以出现微妙的错误,因为微妙的错误是唯一会被发布出去的错误类型。
从终端发布内容省去了我一次上下文切换。而护栏机制让我愿意在去做别的事时让它继续运行。
我用 Claude 起草了这篇文章,然后在发布前对照线上服务器逐条验证了每个声明。playground 响应、工具注解和 OAuth 元数据都是我重新运行验证的,而不是凭记忆。
如果你运行一个对生产环境有写入权限的 agent,你的底线在哪里:一个试运行目标、工具注解,还是人工确认每次调用?
原文:https://dev.to/eugeniya_ivanova_4a58eadc/the-agent-posted-successfully-to-the-wrong-account-3kf3(作者 @eugeniya_ivanova_4a58eadc)