前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
返回 AI 情报前线
All News · 全部资讯9654
  • 给多Agent加上预算限制与人工审批
  • AgentSec 用对抗测试排查智能体安全漏洞
  • 美团如何用统一模型承接外卖多业务精排
  • Safari 空白页如何卡住 MCP 权限校验
  • 会议录音应用的说话人识别与检索踩坑
  • 给 AI 产品文档加一道事实校验关
  • 防止 Agent 靠修改测试制造假通过
  • 故意破坏代码,揭穿回归检查的假绿灯
  • 模型版本变了,评测分差就不能直接比较
  • JetBrains 版 Copilot 增强模型与 MCP 管理
  • 点选界面元素,把修改需求送到对应 Agent
  • 跨公司 Agent 协商,用代码落实数据契约
  • 代码送入远程模型前先做数据分级
  • 把 Agent 权限约束落实到执行层
  • 模型评测暴露环境破坏与联网绕限制行为
  • 微软Decision-1主攻低延迟分类与路由
  • 腾讯元器停服,智能体业务需限期迁移
  • Anthropic暂停全部内部评测联网
  • 模型单价相同,任务成本仍可差近一倍
  • 测 LLM 接口延迟,别只报平均数
  • 用 TypeScript 拦住格式正确的错误数据
  • 微软在 Foundry 推出 Qwen 决策模型
  • 大模型故障切换也要守住能力边界
  • 弱网下如何保住大模型流式响应
  • 用 CI 硬约束控制 AI 代码膨胀
  • Agent 上线前,先验证工具权限边界
  • steadysdk 降低接口客户端再生成风险
  • 生产 Agent 的权限不能照搬人类账号
  • 递归生成可验证的终端 Agent 难题
  • 用缺陷风险排序分配代码审查预算
  • 用真实几何验证模型修复代码的能力
  • 用 Hooks 强制执行 AI 编码质量检查
  • TeamAI 用 Git 同步团队 Agent 配置
  • 为编码助手补充 SwiftUI 开发规范
  • 已加载 34 / 9654
8.0
热点
AI SCORE
技术实践2026-10-11 03:28

Safari 空白页如何卡住 MCP 权限校验

dev.to · AI#MCP#浏览器自动化#权限校验
Editor brief · 编辑速览

safari-mcp 用绑定标签页和来源的凭证约束 Agent 操作,但 Safari 空白标签页缺少 URL,导致正常凭证无法通过来源校验。故障使导航、凭证轮换和关闭操作均被拒绝,作者通过两次单行修改调整守卫逻辑。

文章思维导图
Knowledge map
拖拽缩放
Full translation

完整中文译文

我维护着 safari-mcp,这是一个 MCP server,让 AI Agent 能够操作 Mac 上真正的 Safari,包括其中已登录的会话。10 月 7 日,一个 Agent 打开了一个空白标签页,紧接着对这个标签页执行的下一条命令就被拒绝了:

receipt is not valid for this origin

Agent 的操作完全正确。它调用了不带 URL 的 safari_new_tab(),保存了工具返回的 receipt,再把它传给 safari_navigate。结果,这个标签页既无法导航,也无法换发 receipt,甚至无法关闭。它就这么留在一个有名称的 Safari profile 里,Agent 获准执行的所有操作都动不了它。

那天我发布了两个版本,每个版本都只改了一行代码。这两行都位于一个安全检查中,而我当初就是有意把它设计得很严格。

当 safari-mcp 的 Safari 扩展为 Agent 打开标签页时,会为这个标签页签发一个不透明的 receipt。后续每条命令都要携带这个 receipt,扩展在执行任何操作之前,都会检查两件事:

这个 receipt 属于当前标签页;以及

这个标签页仍然位于签发 receipt 时的 origin。

第二项检查才是关键。Agent 操作的浏览器里,同时还有用户的邮箱、银行账户和管理后台。如果某个页面把 Agent 的标签页重定向到了其他地方,我希望 Agent 下一次执行 evaluate 时被拒绝,而不是在当前加载的未知页面上运行。要继续操作,Agent 必须主动请求一个新的 receipt(getReceipt);而扩展只有在能读取到标签页的 http(s) origin 或 about:blank 时,才会签发。

无法确认安全,就拒绝执行,也就是 fail closed。从设计上看,这正是你想要的行为。

问题出在一个缺失值上

修复之前,扩展是这样推导标签页 origin 的:

function _receiptOrigin(rawUrl) {
  const raw = String(rawUrl || "");
  if (raw === "about:blank") return raw;
  try {
    const parsed = new URL(raw);
    return /^https?:$/.test(parsed.protocol) ? parsed.origin : "";
  } catch {
    return "";
  }
}

空白标签页的 receipt 是基于 about:blank 签发的。我以为 Safari 会把空白标签页的 URL 报告为 about:blank。事实并非如此。它根本不会在这个标签页对象上提供 url 字段。

String(undefined || "") 的结果是 ""。new URL("") 会抛出异常。函数返回 "",而在这段代码中,它表示“完全没有 origin”。"about:blank" !== "",于是检查拒绝了这个标签页自己的 receipt。用于脱困的换发入口调用了同一个函数,得到同样的 "",也拒绝了请求:Cannot issue a receipt for this tab URL。

安全检查完全按照我的设计执行了。只是它无法区分“这个标签页已经跳转到了其他地方”和“Safari 没有告诉我这个标签页在哪里”,于是把两种情况都当成了攻击。

前一天出现的同类问题

10 月 6 日,另一次运行在一个有名称的 profile 中留下了两个标签页,两者都位于 claude.ai/new。这两次都是页面自身通过重定向或登录跳转改变了标签页的位置。只有当新页面具备扩展能够读取的 http(s) origin 时,才能换发 receipt;如果标签页最终落在一个没有这种 origin 的页面上,或者 Safari 没有向扩展提供该页面的 URL,就无法换发。

和其他所有命令一样,close_tab 也受到 receipt 的 origin 约束。拒绝信息建议换发 receipt,但这恰恰是这两个标签页做不到的事。没有任何操作能关闭它们。

这和空白标签页的问题如出一辙:一条适用于在页面中执行代码的命令的规则,被用在了一条不执行页面代码的命令上。

v2.22.12,修复已经跳转的标签页:

const allowReceiptOriginChange = type === "get_tab_receipt" || type === "close_tab";

关闭标签页不会在页面中运行任何东西,因此现在关闭操作会像 getReceipt 一样,通过 receipt 找到对应的标签页,而不是依赖 origin。其他所有命令仍然绑定到 receipt 的 origin,而扩展从未签发过的 receipt 依然无法关闭任何标签页。

v2.22.13,修复空白标签页:

if (!raw || raw === "about:blank") return "about:blank";

现在,Safari 没有提供 URL 的标签页会被视为空白标签页。在网页 origin 上签发的 receipt,仍然不能在这样的标签页上执行任何页面命令,因此,一个从真实网站跳转到空白页面的标签页,仍然会被拒绝。

origin 绑定本身没有问题。问题出在其中的两个假设。

我把“未知”当成了“不匹配”。一个 fail-closed 检查至少有三种结果:匹配、不匹配,或者平台提供的信息不足以判断。我的代码把第三种并入了第二种,因为读取 URL 的函数,对“没有报告 URL”和“这是一个我不信任的 origin”返回了同一个空字符串。一旦这两种情况共用一个值,安全检查迟早会把合法持有者挡在门外。修复方式是在代码中明确规定:对于扩展创建的标签页,缺失 URL 意味着它是空白页。

我让所有命令遵守同一条规则。origin 检查的目的是阻止代码在错误的页面上运行。close_tab 不会在任何页面中执行代码。把它绑定到 origin 没有增加任何保护,却夺走了唯一的退出途径。安全检查的范围应该紧扣它要防止的危害,而且每项安全检查都需要一个不依赖被检查条件的退出途径。

如果测试在 mock 扩展 API 时,规规矩矩地提供了 url: "about:blank",就永远发现不了第一个问题。这两个问题,都是在真实 Safari profile 第一次做出稍微不寻常的行为时暴露出来的,而这也正是这个项目从一开始就选择操作真实浏览器的原因。

如果你使用 safari-mcp 的 Safari 扩展,这两项修复都在扩展内部,因此需要基于 2.22.13 或更高版本重新构建扩展应用,修复才会生效(步骤见 README 的 “Installing the Extension” 一节)。

你自己的安全检查如何表示“我无法判断”?它有独立的值,还是悄悄和“否”共用了一个值?

如果需要采取进一步行动,你可以考虑屏蔽此人和/或举报滥用行为。

Original source

本文由 AI 翻译整理自 dev.to · AI,原文版权归原作者所有。

阅读英文原文
上一篇
美团如何用统一模型承接外卖多业务精排
下一篇
会议录音应用的说话人识别与检索踩坑