摘要: 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 必须最小化。
connectDomains、resourceDomains、frameDomains和baseUriDomains只列业务必需来源,生产环境不得保留通配符和临时开发域名。 - 所有 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.write、insertAdjacentHTML |
不能 | 安全 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 的 Sandbox 到底保护什么
官方架构把 View 放进由宿主控制的沙箱 iframe。它的主要价值是让 App 无法直接读取父页面 DOM、Cookie 或本地存储,也不能在父页面 JavaScript 上下文中执行。宿主与 View 之间的交互走 postMessage 承载的 JSON-RPC,因而可以记录、校验或阻断。
但沙箱内部不是“没有 JavaScript”。MCP Apps 本来就需要脚本实现交互,因此规范的 Sandbox Proxy 会在独立来源上承载 View,并实施声明式 CSP。恶意脚本若在 View 内执行,仍可读取 View 当前收到的工具结果、修改 App UI、调用被公开的 SDK 方法,以及访问 CSP 允许的来源。
这意味着安全边界应分为七层:
- 数据层: 标记用户输入、远端 API、模型输出和工具结果为不可信。
- 渲染层: 默认文本渲染,富文本先清洗,禁止危险 DOM sink。
- iframe 层: 宿主使用独立来源与规范要求的 Sandbox Proxy,不自行弱化隔离。
- CSP 层: 默认拒绝,逐域放行连接、资源、frame 和 base URI。
- 消息层: 校验来源窗口、方法、schema、大小、频率和初始化状态。
- 能力层: 对浏览器权限、工具和上下文更新实施最小授权。
- 业务层: 对高风险动作增加后端授权、幂等、人工确认、审计和回滚。
不要机械套用普通 iframe 的配置
MDN 提醒:同源 iframe 同时启用 allow-scripts 与 allow-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/call、ui/message、上下文更新、链接打开和下载等能力设置策略。协议合规不等于业务授权。
工具调用与权限:被攻陷的 UI 不能继承用户全部能力
MCP Apps 可请求摄像头、麦克风、定位和剪贴板写入等浏览器能力;宿主可以选择是否通过 iframe allow 属性授予。App 必须进行特性检测并在权限被拒时优雅降级,不能把“声明 permissions”当成已经获批。
对工具调用建议分三级:
- 低风险只读: 读取公开数据、重新查询当前面板,可在明确范围内自动执行并限流。
- 中风险内部操作: 创建草稿、写入测试环境、修改非敏感配置,需要显示工具名、参数摘要和目标环境。
- 高风险不可逆: 付款、删除、发邮件、发布、改权限、生产写入,必须由宿主提供不可伪造的原生确认界面,并由服务器再次检查身份、权限、资源范围和短时授权。
界面里的自绘“确认”按钮不是安全确认,因为被攻陷的界面可以伪造点击含义。真正的确认必须位于宿主信任域,并展示规范化后的最终参数。

企业落地:12 步安全实施与验收
以下流程可以直接加入上线清单:
- 建立数据流图。 标出 UI 资源、工具结果、模型输出、数据库字段、第三方 API、消息通道与所有网络出口。
- 建立污点规则。 默认将外部内容、用户输入、持久化自由文本和模型输出标为不可信,直到在具体输出上下文完成处理。
- 搜索危险 sink。 检查
innerHTML、outerHTML、document.write、insertAdjacentHTML、动态脚本、eval、不安全 URL 与模板绕过。 - 改为安全 DOM API。 纯文本用
textContent,属性通过类型化 DOM 属性设置,URL 解析后仅允许业务需要的协议。 - 集中富文本清洗。 使用维护中的清洗库、最小 allowlist 和固定入口,禁止开发者自行写正则过滤 HTML。
- 采用官方 Sandbox Proxy。 宿主与沙箱独立来源,禁止第三方 View 与 AI 客户端共享来源。
- 收紧 CSP。 分别审查连接域、资源域、frame 域与 base URI;删除通配符、测试域及不再使用的 CDN。
- 最小化浏览器权限。 默认不请求 camera、microphone、geolocation、clipboardWrite;按功能逐项启用。
- 验证消息和能力。 使用官方 SDK,限制方法、载荷、频率和生命周期;不把任意消息直接转为工具调用。
- 建立宿主侧审批。 高风险工具在可信 UI 中展示最终参数,并由后端重新授权;加入幂等键、超时与可回滚设计。
- 自动化安全测试。 对存储字段、工具结果、Markdown、SVG、链接、消息、CSP 违规和拒绝权限场景进行回归测试。
- 监控与响应。 记录资源版本哈希、工具名、规范化参数、用户主体、审批结果、网络目标和策略拒绝原因;准备停用资源、撤销令牌及回滚版本的流程。
推荐测试矩阵
| 测试维度 | 必测用例 | 通过标准 |
|---|---|---|
| 文本渲染 | <、>、引号、超长文本、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 结果完成。
参考来源
- MCP Apps 官方概览
- MCP Apps 构建教程
- MCP Apps CSP 与 CORS 官方文档
- SEP-1865:MCP Apps 交互式 UI
- MCP Apps 2026-01-26 规范
- MCP UI 权限接口
- OWASP XSS Prevention Cheat Sheet
- MDN iframe sandbox 安全说明
- MDN Window.postMessage
内容核验日期:2026 年 09 月 23 日
工具选型与提示词资料
适合阅读工具评测、工具推荐、对比测评类文章后继续转化。