一个基于 Safari MCP 构建的自动化工具在读取开发者的仪表板标签页时将其关闭了。这次事件暴露了“防护机制”(guard)中的一个隐藏缺陷,该机制本应防止 AI 驱动的代理(agent)触碰不属于它们的任何标签页。这也说明了为什么“默认安全”的分类可能会成为一种隐患。
曾经奏效的防护机制——直到它失效
该工具会为它创建的每个标签页都打上一个内部标识符。在代理发出任何命令之前,防护机制会检查该标记;如果缺少该标记,防护机制将拒绝执行。在实践中,防护机制成功阻止了代理读取它并未打开的页面——这正是其设计初衷。
在填写表单时,页面重定向到了另一个域名。重定向过程剥离了标记,导致标签页处于未标记状态。防护机制发现标记缺失,并报告:“我无法验证所有权,因此我不会读取此标签页。”在那一刻,安全检查的表现符合预期。
越界的清理代码
接下来是一个旨在关闭“孤儿标签页”(即那些没有标记的标签页)的手动清理程序。该程序要求工具“关闭一个标签页”,但并未事先确认所有权。由于防护机制无法证明该标签页属于它自己,工具便回退到了默认操作:“关闭当前标签页”。而当前的标签页正是开发者正在阅读的仪表板,而非孤儿标签页。
结果,一个本应是死胡同的安全路径,却触发了一次破坏性操作。
三层将“无所有权”视为许可的机制
- 命令分类 – 将命令分组的列表将
close_tab归入了一个宽泛的“标签页管理”类别中。开发者认为该类别下的所有内容都是无害的,因为其他命令(如 “list tabs”)仅读取信息。由于没有明确标注close_tab具有破坏性,它便继承了相邻命令所具有的“安全感”。 - 扩展层策略 – 负责协调所有浏览器操作的 Safari 扩展在会话不拥有任何内容时,允许执行任何操作。该规则适用于只读操作,但也为
close_tab在没有溯源检查(provenance check)的情况下执行打开了大门。 - 逻辑不匹配 – 清理程序检查了其中一个标签页的所有权标志,但随后却对浏览器报告为“当前”的标签页调用了关闭函数。这种不匹配使得防护机制因找不到标记而失败,进而绕过了关闭命令,并将其重定向到了错误的目标。
每一层都假设“未记录所有权”意味着“可以安全操作”,这些因素共同产生了一个在没有任何合法性证明的情况下运行的关闭标签页命令。
修复方案:破坏性操作必须提供所有权证明
修订后的逻辑将只读路径与破坏性路径分离开来。现在,在执行 close_tab 命令之前,工具必须为目标标签页提供有效的标记。如果标记缺失,命令将抛出错误,而不是默认操作当前标签页。防护机制不再回退到通用的“执行某操作”分支。
这一改动消除了标记缺失时可能被解读为“无事可做”或“继续操作”的模糊状态。通过强制执行显式失败,工具保护了用户的工作免受意外丢失。
开发者应注意的事项
- 不要让类别名称决定安全性 – 像“标签页管理”这样的标签无法说明其中每个命令的影响。应在命令本身旁边记录每次操作的代价(读取 vs. 破坏)。
- 防护条件必须与操作的严重程度相匹配 – 对于读取请求而言足够的检查,对于可以删除数据的命令来说是不够的。应为每一类影响程度构建独立的验证流水线。
- 避免隐式回退 – 当防护机制无法验证所有权时,最安全的响应是中止,而不是选择一个默认目标。默认操作是权限提升(privilege-escalation)漏洞的常见来源。
- 审计相邻假设 – 审查任何命令并列显示的列表或菜单。如果代码没有显式地重新评估安全性,一个看似无害的命令可能会继承其相邻命令所获得的信任。
总结
缺失所有权防护机制不是一个 Bug,而是一个设计缺陷。应将每个破坏性命令视为一个独立的安全性领域,要求提供明确的授权证明,绝不要将“无标记”解释为“继续操作”。只有这样,自动化工具才能保护它们本应管理的标签页。
