A2UI 双 iframe 沙箱实践Shared MCP Apps Inner iframe 隔离模式源码与测试解析【免费下载链接】a2ui项目地址: https://gitcode.com/GitHub_Trending/a2/a2uiA2UI 项目在 samples/client/shared/mcp_apps_inner_iframe 目录下维护了一套统一、可复用的沙箱 iframe 实现用于在 Angular、Lit 等 A2UI 客户端中安全运行不受信任的第三方 Model Context ProtocolMCP应用程序。本文以该目录的 README 为主体逐行拆解 sandbox.ts 与 sandbox.html 的实现原理说明双 iframe 隔离模式如何消除浏览器扩展与 DevTools 带来的SecurityError崩溃并给出两套完整的端到端测试启动方案帮助读者在自己的 A2UI 客户端中正确部署并验证这套安全沙箱。为什么需要双 iframe而非单个沙箱 iframe在 A2UI 的应用场景中MCP 服务器会返回text/html;profilemcp-app类型的应用资源例如ui://calculator/app这些 HTML 由第三方提供、内容不受信任因此必须在其完全隔离的环境中渲染。浏览器原生的 iframesandbox属性是最直接的隔离手段但直接嵌入会带来两个问题沙箱 iframe 的 origin 是null未设置allow-same-origin的沙箱 iframe 会被浏览器强制赋予匿名、唯一的 origin序列化为字符串null导致 host 与内嵌页面之间的消息投递、API 桥接都变得棘手。同源场景下的穿透风险如果外层代理 iframe 与 host 同源Angular DevTools、Chrome 扩展等工具会把脚本注入其中一旦这些注入代码触发对受限窗口的访问如读取window.top浏览器会抛出SecurityError直接导致页面崩溃。A2UI 采用的双 iframe 隔离模式double-iframe isolation pattern正是为同时解决这两个问题外层代理 iframeouter proxy加载 sandbox.html与 host同源且不加沙箱负责与 host 通信、做 origin 校验并转发消息内层沙箱 iframeinner iframe由外层动态创建sandboxallow-scripts allow-forms allow-popups allow-modals严格省略allow-same-origin承载不受信任的 MCP 应用 HTML 内容。从源码结构看sandbox.ts外层代理通过document.createElement(iframe)创建内层 iframe随后用inner.srcdoc html将第三方内容写入。这种外层可信、内层隔离的结构既消除了 DevTools/扩展注入引发的SecurityError崩溃又通过内层 iframe 对不受信任内容保持了严格隔离。sandbox 指令的安全边界内层 iframe 通过inner.setAttribute(sandbox, allow-scripts allow-forms allow-popups allow-modals)sandbox.ts初始化其安全边界设计要点如下显式省略allow-same-origin这是隔离的关键。一旦授予该权限沙箱 iframe 会恢复与其父级的同源关系storagelocalStorage/sessionStorage、cookie、origin 等将全部可被第三方脚本访问。严格省略它才能将不受信任内容与宿主站点的存储与源隔离。此时内层 iframe 的 origin 被强制为null这也是后文消息中继必须使用postMessage(..., *)的原因。显式省略allow-top-navigation与allow-top-navigation-by-user-activation用于阻止顶层窗口劫持frame-busting。若授予这两项权限嵌入的脚本可执行window.top.location ...或将链接导航到_top从而把用户引导至钓鱼页面。A2UI 在内层沙箱中严格禁用这两项从机制上杜绝了此类攻击。allow-scripts放行脚本执行MCP 应用是动态 HTML/JS 内容必须允许脚本运行allow-forms、allow-popups、allow-modals则按应用实际需要放行表单提交、弹窗与模态框。需要注意的是宿主侧在推送内容时还可以按资源动态收紧 sandbox 指令。Angular 客户端在 mcp-app.ts 中通过bridge.sendSandboxResourceReady({ html, sandbox: allow-scripts })发送资源时显式指定了sandbox: allow-scripts仅保留脚本执行能力外层收到后执行inner.setAttribute(sandbox, sandbox)sandbox.ts完成覆盖。可以推断不同的 MCP 应用资源可携带不同的 sandbox 策略默认仅允许脚本需要表单或弹窗时再由宿主按需放宽。sandbox.ts 源码逐段解析sandbox.ts 是外层代理 iframe 的逻辑核心其职责可归纳为三层启动守卫 → 安全自检 → 消息中继。1. 启动守卫只允许在 iframe 沙箱中使用if (window.self window.top) { throw new Error(This file is only to be used in an iframe sandbox.); } if (!document.referrer) { throw new Error(No referrer, cannot validate embedding site.); } if (!document.referrer.match(ALLOWED_REFERRER_PATTERN)) { throw new Error( Embedding domain not allowed in referrer ${document.referrer}. Expected sandbox environment configuration., ); }对应源码见 sandbox.ts。该文件只能在 iframe 环境中被加载——如果被直接以顶层窗口打开window.self window.top立即抛错拒绝运行。随后通过document.referrer校验嵌入站点是否在白名单内const allowedHostOrigin meta meta.env ? meta.env.VITE_ALLOWED_HOST_ORIGIN : undefined; const ALLOWED_REFERRER_PATTERN allowedHostOrigin ? new RegExp(^${allowedHostOrigin.replace(/[.*?^${}()|[\]\\]/g, \\$)}) : /^http:\/\/(localhost|127\.0\.0\.1)(:|\/|$)/;对应源码见 sandbox.ts。生产环境下可通过 Vite 环境变量VITE_ALLOWED_HOST_ORIGIN配置允许的宿主 origin字符串中的正则元字符会被转义后构造成^...前缀匹配未配置时默认仅允许http://localhost与http://127.0.0.1的任意端口或路径。referrer 校验通过后EXPECTED_HOST_ORIGIN取 referrer 的 origin作为后续一切父级消息来源的校验基准。2. 安全自检Security Self-Testconst disableSelfTest urlParams.get(disable_security_self_test) true; if (!disableSelfTest) { try { window.top!.alert(If you see this, the sandbox is not setup securely.); throw FAIL; } catch (e) { if (e FAIL) { throw new Error(The sandbox is not setup securely.); } } }对应源码见 sandbox.ts。这是一个精巧的隔离有效性验证在正确配置的沙箱中内层/外层 iframe 都无法访问window.top因此window.top.alert(...)必然抛出异常若它没有抛错即 alert 成功弹出说明隔离失效直接以 The sandbox is not setup securely. 终止运行。该自检可通过 URL 查询参数disable_security_self_testtrue关闭Angular 客户端即以此参数运行自动化测试详见 mcp-app.ts。3. 双 iframe 消息中继外层代理维护一条双向消息通道父窗口Host⇄ 外层代理 ⇄ 内层沙箱。核心监听器见 sandbox.tsHost → 内层event.source window.parent时先用EXPECTED_HOST_ORIGIN校验消息来源并将127.0.0.1归一化为localhost以避免开发环境 origin 不一致不匹配即拒绝。随后识别ui/notifications/sandbox-resource-ready通知即RESOURCE_READY_NOTIFICATION从event.data.params中取出html、sandbox、permissionssandbox为非空字符串时覆盖内层 iframe 的 sandbox 属性通过buildAllowAttribute(permissions)从权限对象生成allow属性如摄像头、麦克风等 feature policy非空时写入内层 iframehtml为非空字符串时设置inner.srcdoc html并在onload后向内层发送{ type: sandbox-init }初始化消息其他消息则原样转发给内层。内层 → Hostevent.source inner.contentWindow时校验event.origin必须等于OWN_ORIGIN或null——由于内层沙箱缺少allow-same-origin浏览器会将其 origin 序列化为null所以必须显式放行字符串null安全验证主要依赖event.source inner.contentWindow这一强引用检查。通过后转发给window.parent目标 origin 固定为EXPECTED_HOST_ORIGIN。两个*的必要性无论 Host 向内层投递sandbox-init还是外层向内层转发任意消息目标 origin 都必须写*sandbox.ts。因为未启用allow-same-origin的沙箱窗口 origin 为null若指定具体 origin 会因不匹配导致投递失败。安全性并不因此降低——反向校验来源端始终是严格的。4. 就绪通知Proxy Readywindow.parent.postMessage( { jsonrpc: 2.0, method: PROXY_READY_NOTIFICATION, // ui/notifications/sandbox-proxy-ready params: {}, }, EXPECTED_HOST_ORIGIN, );对应源码见 sandbox.ts。外层代理加载完成后以 JSON-RPC 2.0 格式向 Host 广播ui/notifications/sandbox-proxy-readyHost 收到后即可开始通过桥接层发送资源内容。RESOURCE_READY_NOTIFICATIONui/notifications/sandbox-resource-ready与PROXY_READY_NOTIFICATION这两个方法名均来自modelcontextprotocol/ext-apps/app-bridge的类型定义McpUiSandboxResourceReadyNotification/McpUiSandboxProxyReadyNotification说明沙箱与 Host 的握手协议由该 MCP 扩展包统一定义。sandbox.html外层代理容器sandbox.html 是外层代理 iframe 的载体重点在于其 Content-Security-Policysandbox.htmlmeta http-equivContent-Security-Policy contentdefault-src self unsafe-inline unsafe-eval data: blob:; child-src self blob:; frame-ancestors *; /default-src self unsafe-inline unsafe-eval data: blob:允许从自身 origin、data:与blob:URI 加载资源并放行内联脚本与动态求值MCP 应用 HTML 往往依赖内联脚本child-src self blob:限定内层 iframe 的来源仅允许同源或blob:frame-ancestors *允许该代理 iframe 被任意站点嵌入Host 侧合法性由sandbox.ts的 referrer 校验兜底。页面其余部分仅为样式html/body透明背景、iframe占满容器且color-scheme: inheritsandbox.html保证外层透明、内层内容可见页面主体只加载sandbox.js即编译后的 sandbox.ts。与 Host 的桥接集成以 Angular 客户端为例沙箱并非孤立组件它通过modelcontextprotocol/ext-apps的AppBridge与 Host 集成。在 Angular 示例 mcp-app.ts 中可以看到完整的集成链路组件在模板中渲染iframe [src]iframeSrc()iframeSrc指向${currentOrigin}/mcp_apps_inner_iframe/sandbox.html开发测试时追加?disable_security_self_testtrueAppBridge初始化时不直接连接 MCP Serverconst emptyMcpClient null注释明确说明MCP Server 的通信由沙箱 iframe 负责当渲染器服务解析出htmlContent后通过bridge.sendSandboxResourceReady({ html, sandbox: allow-scripts })将内容推送给外层代理再由外层写入内层 iframe。这与 sandbox.ts 处理sandbox-resource-ready通知的逻辑一一对应构成了Host → 外层代理 → 内层沙箱的完整数据通路。同一份mcp_apps_inner_iframe实现还被复制到社区示例目录 samples/community/client/shared/mcp_apps_inner_iframe/该目录下额外包含 sandbox.spec.ts 单元测试并由 samples/community/client/lit/mcp-apps-in-a2ui-sample/ 等 Lit 客户端复用印证了其跨框架统一复用的定位。端到端测试指南按照 README 的约定修改本目录的任何代码后必须启动相关客户端与服务端做端到端验证沙箱行为强依赖真实浏览器环境无法仅靠单测覆盖。下面两个用例对应两种不同的安全关注面。用例一Contact Multi-Surface 样例Lit 客户端 ADK Agent该用例验证沙箱与Lit 客户端 ADK A2A Agent的组合重点考察多 surface 场景下的隔离与消息中继组件路径启动命令说明A2A Agent Serversamples/agent/adk/contact_multiple_surfaces/uv run .需要.env中配置GEMINI_API_KEYLit Client Appsamples/client/lit/contact/yarn dev需先构建 Lit renderer访问地址http://localhost:5173/—Vite 默认端口路径说明README 给出的原相对路径为../../../agent/adk/contact_multiple_surfaces/与../../lit/contact/以samples/client/shared/mcp_apps_inner_iframe/为基准。读者在本仓库中按上述以仓库根目录为准的路径查找即可。用例二MCP AppsCalculator样例Angular该用例验证Angular 客户端 MCP Proxy Agent 远端 MCP Server的三段式链路组件路径启动命令说明MCP ServerCalculatorsamples/community/mcp/mcp-apps-calculator/uv run .默认运行在 8000 端口暴露ui://calculator/app资源MCP Apps Proxy Agentsamples/agent/adk/mcp_app_proxy/uv run .需要.env中配置GEMINI_API_KEYAngular Client Appsamples/client/angular/yarn start -- mcp_calculator需先执行yarn build:sandbox与yarn install访问地址http://localhost:4200/?disable_security_self_testtrue—追加该参数以跳过安全自检便于自动化测试该链路的关键点在于MCP Server 本身只是资源提供方Calculator 应用的 HTML 由 mcp-apps-calculator 在构建阶段打包进calculator.htmlAgent 负责获取资源并转发给客户端客户端再经由沙箱 iframe 渲染。在 mcp-app.ts 中可以看到sendSandboxResourceReady只携带html与sandbox: allow-scripts未传permissions说明 Calculator 应用仅需脚本能力即可运行。常见配置要点与注意事项生产环境务必配置VITE_ALLOWED_HOST_ORIGIN默认白名单仅覆盖 localhost上线前应在构建外层沙箱时通过该环境变量指定真实宿主域名否则 referrer 校验会拒绝加载。不要给内层 iframe 追加allow-same-origin这是整条隔离链的地基一旦放开storage、cookie 与 origin 全部暴露给第三方脚本安全自检也会形同虚设。消息投递的目标 origin 用*、来源校验保持严格内层沙箱 origin 为null只能以*投递但所有入站消息都必须经过event.source引用与 origin 双重校验Host 侧校验EXPECTED_HOST_ORIGIN内层侧校验OWN_ORIGIN或null。测试环境使用disable_security_self_testtrue该参数会关闭window.top.alert自检避免自动化测试环境因隔离提示弹窗而挂起但它不降低沙箱本身的安全级别仅跳过自检动作。综上A2UI 的 Shared MCP Apps Inner iframe 是一套同源代理 无同源沙箱的经典纵深防御实践外层代理负责可信通信与校验内层沙箱负责严格隔离两层各司其职、缺一不可。读者既可以直接复用 samples/client/shared/mcp_apps_inner_iframe/ 下的现成实现也可以参照其消息协议与安全边界为自己的客户端实现同等强度的第三方 MCP 应用隔离。【免费下载链接】a2ui项目地址: https://gitcode.com/GitHub_Trending/a2/a2ui创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考