MCP Apps 安全教程封面,展示 AI 客户端中的 HTML 面板、XSS 风险与 iframe Sandbox 防护

MCP Apps 安全教程:AI 客户端里的 HTML、Stored XSS 与 Sandbox 怎么防

MCP Apps 不应只依赖 iframe 沙箱。本文提供从安全 DOM 渲染、HTML 清洗、CSP、消息校验到工具授权与审计的完整防护方案。

摘要: MCP Apps 把仪表盘、表单和审批界面直接带进 AI 客户端,也把传统 Web 的 HTML 注入、Stored XSS、危险 DOM API 与跨窗口消息风险带进了 Agent 工作流。最重要的结论是:iframe Sandbox 负责隔离,CSP 负责缩小资源与联网范围,输出编码和 HTML 清洗负责阻止脚本进入 DOM,而工具授权、人工确认与审计负责阻止被攻陷的界面借宿主执行高风险动作。本文适合 MCP Server 开发者、AI 客户端团队和企业安全人员,可直接用于设计、测试和验收 MCP App;建议在生产接入前完成本文的七层防护与测试矩阵。

核心结论

MCP Apps 的 HTML 必须同时按“不可信网页”和“Agent 工具入口”治理。只给 iframe 加 sandbox 并不能解决 Stored XSS;如果应用把数据库、工具结果或模型输出直接写入 innerHTML,恶意内容仍可能在 App 自己的隔离域中执行,并通过被允许的 JSON-RPC、网络出口或工具调用扩大影响。

  • 值得使用,但必须安全默认。 MCP Apps 适合数据可视化、复杂表单和多步审批;宿主应默认拒绝网络、嵌套 iframe 和敏感浏览器权限。
  • Stored XSS 的根因不在 iframe。 根因是“不可信持久化数据进入危险 HTML/DOM 上下文”;首选 textContent,确需富文本时使用维护中的 HTML 清洗库并限制标签、属性和 URL 协议。
  • Sandbox 是损害控制,不是输入过滤器。 它隔离父页面 DOM、Cookie 与存储,但 App 内执行的恶意脚本仍可能看到该 App 收到的数据、触发界面欺骗或请求允许的能力。
  • CSP 必须最小化。 connectDomainsresourceDomainsframeDomainsbaseUriDomains 只列业务必需来源,生产环境不得保留通配符和临时开发域名。
  • 所有 App 发起的高风险工具调用都要在宿主侧重新授权。 删除、付款、发信、发布、改权限及生产写操作不得因“来自已渲染 App”而自动可信。

MCP Apps 为什么让 XSS 问题重新变得重要

传统 MCP 工具主要返回文本、结构化数据或资源。MCP Apps 增加了交互 UI:工具通过 _meta.ui.resourceUri 指向一个 ui:// HTML 资源,宿主获取资源后在对话内渲染,工具结果再进入该界面。界面还可通过 MCP Apps 的通信协议请求工具、发送消息或更新上下文。

这带来两种信任边界的叠加:第一层是浏览器边界,HTML、CSS、JavaScript 和外部资源可能含主动内容;第二层是 Agent 边界,界面不仅展示信息,还可能间接影响模型上下文和工具执行。普通后台页面里的 XSS 已经危险,而嵌入 AI 客户端的 XSS 还可能利用“用户相信 AI 客户端”的心理,伪造确认按钮、诱导授权或把恶意字符串送回模型。

官方 MCP Apps 文档明确采用沙箱 iframe、预声明模板、可审计 JSON-RPC 和用户同意等多层安全模型。这里要避免一个误读:官方所说的“安全渲染”并不等于 App 开发者可以放心使用 innerHTML。宿主隔离的是 App 与父客户端;App 自身仍需承担正常 Web 应用的 XSS 防护责任。

可继续阅读 AI Stack Nav 的 MCP 安全相关文章AI Agent 安全沙箱教程,把本文放进更完整的工具权限与网络出口治理体系。

三类风险:HTML 注入、Stored XSS 与能力滥用

风险 常见数据来源 典型危险点 Sandbox 能否单独解决 首要控制
HTML 注入 模型输出、工具结果、用户备注 innerHTML、模板字符串 不能 上下文编码、使用安全 DOM API
Stored XSS 工单、知识库、项目描述、评论 数据持久化后被多人界面加载 不能 写入验证、读取编码、富文本清洗
DOM XSS URL、hash、消息事件、客户端状态 document.writeinsertAdjacentHTML 不能 安全 sink、消息 schema 校验
跨域资源滥用 外部脚本、图片、字体、WebSocket 过宽 CSP、通配符域名 部分 最小 CSP、代理与出口控制
UI 发起工具滥用 被攻陷的按钮或脚本 tools/call、上下文更新 部分 宿主授权、风险分级、人工确认
点击劫持/仿冒确认 恶意 UI、嵌套 iframe 伪造宿主样式与审批按钮 部分 清晰边界、禁止嵌套 frame、宿主确认

HTML 注入不一定执行脚本,但仍可欺骗用户

攻击者不必成功运行 <script> 才有价值。插入一个伪造的“重新登录”表单、覆盖真实按钮、创建透明点击层或加载跟踪图片,都可能造成凭据诱导、误审批和信息泄露。因此,安全测试不能只检查 alert(1) 是否弹出,还要检查样式覆盖、表单提交、导航、图片回连、SVG 事件和 URL 协议。

Stored XSS 为什么更危险

Stored XSS 的恶意内容先进入数据库、知识库或第三方系统,再在后续会话中被其他用户的 MCP App 加载。攻击可能跨用户、跨时间反复触发,且数据看起来来自“内部可信系统”。例如,客服工单标题、GitHub Issue、CRM 客户名称或监控告警正文都可能成为持久化载体。

在 AI 场景中,模型生成内容也必须视为不可信。模型可能复述攻击者放进文档的 HTML,工具可能把远端字段原样返回,服务器也可能把 Markdown 渲染结果当成安全 HTML。信任应根据数据流而不是“由谁最后返回”决定。

MCP Apps 中 Stored XSS 从不可信数据进入 HTML、跨会话触发并调用宿主能力的攻击链
Stored XSS 攻击链:恶意内容被持久化,经工具结果进入 App DOM,随后尝试利用网络或宿主能力扩大影响。

MCP Apps 的 Sandbox 到底保护什么

官方架构把 View 放进由宿主控制的沙箱 iframe。它的主要价值是让 App 无法直接读取父页面 DOM、Cookie 或本地存储,也不能在父页面 JavaScript 上下文中执行。宿主与 View 之间的交互走 postMessage 承载的 JSON-RPC,因而可以记录、校验或阻断。

但沙箱内部不是“没有 JavaScript”。MCP Apps 本来就需要脚本实现交互,因此规范的 Sandbox Proxy 会在独立来源上承载 View,并实施声明式 CSP。恶意脚本若在 View 内执行,仍可读取 View 当前收到的工具结果、修改 App UI、调用被公开的 SDK 方法,以及访问 CSP 允许的来源。

这意味着安全边界应分为七层:

  1. 数据层: 标记用户输入、远端 API、模型输出和工具结果为不可信。
  2. 渲染层: 默认文本渲染,富文本先清洗,禁止危险 DOM sink。
  3. iframe 层: 宿主使用独立来源与规范要求的 Sandbox Proxy,不自行弱化隔离。
  4. CSP 层: 默认拒绝,逐域放行连接、资源、frame 和 base URI。
  5. 消息层: 校验来源窗口、方法、schema、大小、频率和初始化状态。
  6. 能力层: 对浏览器权限、工具和上下文更新实施最小授权。
  7. 业务层: 对高风险动作增加后端授权、幂等、人工确认、审计和回滚。

不要机械套用普通 iframe 的配置

MDN 提醒:同源 iframe 同时启用 allow-scriptsallow-same-origin 可能削弱 sandbox。MCP Apps 规范采用“宿主与 Sandbox 必须不同源”的架构,并由 Sandbox Proxy 加载 View,因此实现宿主时应遵循官方 SDK/规范,不要把第三方 HTML 直接用 srcdoc 塞进与客户端同源的 iframe,也不要自行组合一个看似相同但信任边界不同的沙箱。

安全渲染:从 innerHTML 改成数据到 DOM

下面是不安全示例。result.summary 可能来自数据库、外部网页、模型输出或工具结果;一旦含主动 HTML,就进入解释器上下文。

app.ontoolresult = (result) => {
  const data = result.structuredContent as { summary?: string };
  document.querySelector("#summary")!.innerHTML = data.summary ?? "";
};

纯文本应改用 textContent

app.ontoolresult = (result) => {
  const data = result.structuredContent as { summary?: unknown };
  const summary = typeof data.summary === "string" ? data.summary : "";
  document.querySelector("#summary")!.textContent = summary;
};

如果业务确实需要富文本,应把 Markdown 转换和 HTML 清洗分开,并严格限制允许项。以下代码只是模式示例,实际项目必须锁定并持续更新清洗库版本。

import DOMPurify from "dompurify";

function renderTrustedSubset(target: HTMLElement, dirtyHtml: string) {
  const clean = DOMPurify.sanitize(dirtyHtml, {
    ALLOWED_TAGS: ["p", "strong", "em", "ul", "ol", "li", "code", "pre", "a"],
    ALLOWED_ATTR: ["href", "title"],
    ALLOW_DATA_ATTR: false,
  });
  target.innerHTML = clean;
  for (const link of target.querySelectorAll<HTMLAnchorElement>("a")) {
    const url = new URL(link.href, location.href);
    if (!["https:"].includes(url.protocol)) link.removeAttribute("href");
    link.rel = "noopener noreferrer";
  }
}

不要在清洗后再次拼接字符串,也不要把清洗结果送入会重新解释或变形 HTML 的第三方组件。前端清洗是最后一道渲染防线,后端仍应对字段长度、类型与业务格式做验证;但输入验证不能替代上下文相关输出编码。

CSP、CORS 与网络出口的正确关系

MCP Apps HTML 作为 MCP 资源运行,没有普通 Web 应用意义上的同源后端。官方文档要求,网络请求通过 _meta.ui.csp 声明:connectDomains 用于 fetch、XHR 和 WebSocket,resourceDomains 用于脚本、样式、图片与字体;需要嵌套 iframe 或特定 base URI 时再声明对应域名。未声明时应使用限制性默认值。

registerAppResource(server, "安全工单面板", "ui://ticket/view.html", {}, async () => ({
  contents: [{
    uri: "ui://ticket/view.html",
    mimeType: "text/html;profile=mcp-app",
    text: bundledHtml,
    _meta: {
      ui: {
        csp: {
          connectDomains: ["https://api.example.com"],
          resourceDomains: ["https://static.example.com"],
          frameDomains: [],
          baseUriDomains: []
        },
        permissions: {}
      }
    }
  }]
}));

CSP 决定浏览器“允许向哪里连接或从哪里加载”,CORS 决定目标 API“是否接受这个来源”。两者不能互相替代。更不能把 CSP 当作 XSS 主防线:OWASP 明确将 CSP 定位为纵深防御,主要防护仍是安全编码和清洗。

生产建议把第三方脚本打包进单文件,减少 resourceDomains;把必须访问的 API 收敛到企业网关;对上传、WebSocket 和重定向分别测试。若需要稳定来源用于 CORS 或 OAuth 回调,可使用 _meta.ui.domain,但格式和支持情况由宿主决定,不能假设所有客户端一致。

postMessage 与 JSON-RPC:不要信任“来自 iframe”的消息

直接开发宿主或桥接层时,必须把消息视为外部输入。至少验证 event.source 是否为目标 iframe、消息是否符合 JSON-RPC schema、方法是否在允许列表、请求是否发生在完成初始化之后,以及载荷大小与速率是否合理。不要使用一个全局监听器把任意 event.data 转发到 MCP Server。

window.addEventListener("message", (event) => {
  if (event.source !== appFrame.contentWindow) return;
  if (!isJsonRpcMessage(event.data)) return;
  if (!initialized) return;
  if (!ALLOWED_METHODS.has(event.data.method)) return;
  if (JSON.stringify(event.data).length > 64_000) return;
  bridge.handle(event.data);
});

成熟实现应优先采用官方 MCP Apps SDK,由协议层完成生命周期与传输处理。即便如此,宿主仍需要对 tools/callui/message、上下文更新、链接打开和下载等能力设置策略。协议合规不等于业务授权。

工具调用与权限:被攻陷的 UI 不能继承用户全部能力

MCP Apps 可请求摄像头、麦克风、定位和剪贴板写入等浏览器能力;宿主可以选择是否通过 iframe allow 属性授予。App 必须进行特性检测并在权限被拒时优雅降级,不能把“声明 permissions”当成已经获批。

对工具调用建议分三级:

  • 低风险只读: 读取公开数据、重新查询当前面板,可在明确范围内自动执行并限流。
  • 中风险内部操作: 创建草稿、写入测试环境、修改非敏感配置,需要显示工具名、参数摘要和目标环境。
  • 高风险不可逆: 付款、删除、发邮件、发布、改权限、生产写入,必须由宿主提供不可伪造的原生确认界面,并由服务器再次检查身份、权限、资源范围和短时授权。

界面里的自绘“确认”按钮不是安全确认,因为被攻陷的界面可以伪造点击含义。真正的确认必须位于宿主信任域,并展示规范化后的最终参数。

MCP Apps 从不可信数据、渲染、Sandbox、CSP、消息校验到工具授权的七层防御流程
MCP Apps 七层防御闭环:每层解决不同问题,任何单层都不能替代端到端授权与审计。

企业落地:12 步安全实施与验收

以下流程可以直接加入上线清单:

  1. 建立数据流图。 标出 UI 资源、工具结果、模型输出、数据库字段、第三方 API、消息通道与所有网络出口。
  2. 建立污点规则。 默认将外部内容、用户输入、持久化自由文本和模型输出标为不可信,直到在具体输出上下文完成处理。
  3. 搜索危险 sink。 检查 innerHTMLouterHTMLdocument.writeinsertAdjacentHTML、动态脚本、eval、不安全 URL 与模板绕过。
  4. 改为安全 DOM API。 纯文本用 textContent,属性通过类型化 DOM 属性设置,URL 解析后仅允许业务需要的协议。
  5. 集中富文本清洗。 使用维护中的清洗库、最小 allowlist 和固定入口,禁止开发者自行写正则过滤 HTML。
  6. 采用官方 Sandbox Proxy。 宿主与沙箱独立来源,禁止第三方 View 与 AI 客户端共享来源。
  7. 收紧 CSP。 分别审查连接域、资源域、frame 域与 base URI;删除通配符、测试域及不再使用的 CDN。
  8. 最小化浏览器权限。 默认不请求 camera、microphone、geolocation、clipboardWrite;按功能逐项启用。
  9. 验证消息和能力。 使用官方 SDK,限制方法、载荷、频率和生命周期;不把任意消息直接转为工具调用。
  10. 建立宿主侧审批。 高风险工具在可信 UI 中展示最终参数,并由后端重新授权;加入幂等键、超时与可回滚设计。
  11. 自动化安全测试。 对存储字段、工具结果、Markdown、SVG、链接、消息、CSP 违规和拒绝权限场景进行回归测试。
  12. 监控与响应。 记录资源版本哈希、工具名、规范化参数、用户主体、审批结果、网络目标和策略拒绝原因;准备停用资源、撤销令牌及回滚版本的流程。

推荐测试矩阵

测试维度 必测用例 通过标准
文本渲染 <>、引号、超长文本、Unicode 作为文本显示,不改变 DOM 结构
富文本 事件属性、SVG、危险 URL、嵌套标签 危险节点与属性被移除
持久化 攻击字符串写入后由另一账号读取 不执行,审计能定位来源记录
CSP 未声明 API、脚本、图片、frame 浏览器阻断并产生可观测事件
权限 摄像头/麦克风/定位被拒 功能降级,不绕过、不反复诱导
消息 错误 source、未知 method、超大载荷 宿主拒绝且不调用工具
工具调用 删除、发信、生产写入 必须经过宿主确认和服务端授权
回滚 新 UI 资源出现异常 能按版本快速禁用或回退

测试时只使用隔离环境和无害标记字符串,不要对第三方或生产客户端进行未授权攻击。Stored XSS 回归应验证“内容没有被解释执行”,而不是追求真实数据外传。

成本、性能与可维护性

安全控制会增加少量开发与运行成本,但最昂贵的不是 DOMPurify 或 CSP,而是没有统一渲染组件导致每个 App 重复犯错。建议平台团队提供安全文本组件、富文本组件、链接组件、消息 schema、权限策略和工具确认 API,让业务团队默认走安全路径。

单文件打包可以减少外部依赖和 CSP 配置,但会增加资源体积与更新粒度;外部静态资源便于缓存,却扩大供应链与域名白名单。企业可按风险选择:敏感内部 App 优先单文件和自有域;公共展示 App 可使用固定 CDN,但需要完整性、版本锁定、变更审查和故障回退。

清洗和 schema 校验通常不是性能瓶颈。对于大块日志、富文本或实时流,采用大小上限、分页、虚拟滚动和分段清洗;不要为了性能跳过清洗,也不要把整个原始工具结果长期存入浏览器存储。重试必须有上限和退避,写操作使用幂等键,超时后先查询状态再决定是否重试。

局限、故障排查与应急响应

宿主支持存在差异。MCP Apps 是可选扩展,权限、稳定域名、CSP 展现和确认交互可能因客户端而异;App 应提供文本回退,且不得把关键安全检查只放在前端。服务端必须对每次工具调用执行身份和权限校验。

常见故障可按顺序排查:先看宿主是否协商 MCP Apps 能力,再确认 ui:// 资源、MIME 类型和 _meta.ui.resourceUri;随后检查 CSP 违规、CORS 响应、权限拒绝和初始化握手;最后检查消息 schema 与工具授权。开发时临时放宽 CSP 的配置不得进入生产。

发现疑似 Stored XSS 后,应立即停止分发受影响 UI 版本,禁用高风险 App 能力,保全资源版本、数据库记录、工具调用和网络日志;定位污染数据入口与受影响用户,清洗或隔离历史记录,撤销可能泄露的令牌,并发布经过回归测试的新版本。不要只删除一条 payload 后恢复服务,因为相同 sink 可能还有其他载体。

事实依据与来源

  • 官方已确认: MCP Apps 使用 ui:// 资源、沙箱 iframe 和基于 JSON-RPC 的双向通信;宿主可预取模板并进行安全审查。
  • 官方已确认: MCP Apps CSP 将连接域与静态资源域分开声明;浏览器权限由资源请求,宿主可以选择是否授予。
  • 官方规范要求: Host 与 Sandbox 使用不同来源,Sandbox 对 View 应用声明域名和限制性默认 CSP,并阻断 object-src 等危险能力。
  • OWASP 指导: 不可信数据应使用上下文相关输出编码;确需渲染 HTML 时使用专用清洗库,CSP 只作为纵深防御。
  • 编辑判断: Stored XSS 与 UI 发起工具调用结合后,风险可能从“界面内执行”升级为“利用 Agent 能力执行操作”,实际影响取决于宿主授权和服务端权限设计。
  • 实施建议: 七层防御、三级工具风险与 12 步验收流程是本文根据官方机制整理的工程方案,并非 MCP 官方强制分级。

内容核验日期:2026 年 09 月 23 日。 具体宿主实现、SDK 版本与扩展能力可能继续变化,上线前应重新核对官方规范和目标客户端文档。

FAQ

MCP Apps 的 HTML 默认就是安全的吗?

不是。宿主提供的沙箱能隔离父客户端,但 HTML 内部仍可能发生 XSS。App 开发者必须对工具结果、数据库内容、模型输出和消息数据实施安全渲染、清洗、CSP 与权限控制。

iframe Sandbox 能彻底防住 Stored XSS 吗?

不能。它通常能阻止恶意脚本读取父页面 Cookie 或 DOM,却不能阻止脚本读取 App 已收到的数据、篡改 App 界面或请求 App 被允许的工具和网络能力。Sandbox 是影响面控制,不是 XSS 修复。

所有动态内容都应该使用 textContent 吗?

纯文本应优先使用 textContent。只有确实需要富文本时才使用 innerHTML,而且输入必须经过维护中的 HTML 清洗库和最小 allowlist;清洗后不得再次进行不安全拼接。

CSP 配好了,还需要 DOMPurify 吗?

需要。CSP 可能因配置错误、受信任域被攻陷或浏览器差异而失效,且不能解决所有 HTML 欺骗。OWASP 将 CSP 视为纵深防御;安全 sink、输出编码和 HTML 清洗仍是主要控制。

MCP App 可以直接访问外网吗?

取决于宿主和资源声明。应用需要在 _meta.ui.csp.connectDomains 中声明 fetch、XHR 或 WebSocket 的目标来源,目标服务器还可能要求正确 CORS。企业环境应再叠加网关和网络出口策略。

permissions 声明后就一定能使用摄像头或麦克风吗?

不一定。官方接口把它定义为请求,宿主可以不授予。应用必须进行特性检测,提供拒绝后的降级路径,且不应在无明确功能需要时请求敏感权限。

UI 内的确认按钮能用于批准删除或付款吗?

不能作为最终安全确认。被攻陷的 UI 可以伪造按钮和参数。删除、付款、发信、发布或生产写入应由宿主可信界面展示最终参数并确认,服务端随后再次检查用户身份和权限。

如何发现历史数据中的 Stored XSS 风险?

先盘点所有自由文本和富文本字段,再追踪它们进入 App DOM 的路径;用静态扫描查危险 sink,用隔离账号加载历史记录做回归,并记录 DOM 变化、CSP 违规、网络请求和工具调用。不要在生产中使用可执行攻击代码。

不支持 MCP Apps 的客户端怎么办?

工具应提供有意义的文本或结构化数据回退。MCP Apps 是渐进增强而不是必需能力,服务端可根据能力协商注册或返回 UI,同时保证核心操作仍能通过普通 MCP 结果完成。

参考来源

  1. MCP Apps 官方概览
  2. MCP Apps 构建教程
  3. MCP Apps CSP 与 CORS 官方文档
  4. SEP-1865:MCP Apps 交互式 UI
  5. MCP Apps 2026-01-26 规范
  6. MCP UI 权限接口
  7. OWASP XSS Prevention Cheat Sheet
  8. MDN iframe sandbox 安全说明
  9. MDN Window.postMessage

内容核验日期:2026 年 09 月 23 日

工具评测文章

工具选型与提示词资料

适合阅读工具评测、工具推荐、对比测评类文章后继续转化。

工具选型表 按场景、价格、上手难度和核心能力筛选合适的 AI 工具。 查看资料包 提示词模板包 提供写作、运营、编程、图片和视频生成常用提示词模板。 查看资料包

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

本站累计访问量: 366,632
AI Stack Nav 客服会员 / 支付 / 下载 / 工具库
你好,我是 AI Stack Nav 客服助手。你可以问我会员开通、微信支付、资料下载、订单入口、AI 工具库等问题。