先看结论与判断条件
- 模型输出、检索片段和工具结果都是内容数据,不能因为使用了 system instruction 或来自自有服务就进入可信代码通道。
- 优先把 Markdown 解析成受控 AST 并交给原生组件渲染;无法识别的节点、原始 HTML、事件属性和嵌入对象默认拒绝。
- 若业务必须使用 WebView,渲染实例与登录页、支付页、客服页等可信 WebView 分开,默认禁用 JavaScript、文件访问和 Native bridge。
- URL 需要按 scheme、host、动作类型和用户意图分别判断;展示链接不等于允许自动导航、下载、打开深链或调用宿主能力。
- 清洗、渲染、点击和外跳要使用同一 policyVersion,并记录有限决策回执,避免内容通过缓存或重试绕过最新策略。
- Data safety、第三方 SDK 清单和发布证据要反映真实数据行为,但声明与静态校验不能替代目标 App 的运行验证。
先把模型文本从代码与权限通道中分离
AI 回复看起来像普通文字,但其中可能包含 Markdown 链接、图片、原始 HTML、代码块、表格、内联样式或带参数的 URL。模型并不知道移动端渲染器的全部权限,也不能判断某段内容是否会被 WebView 当作活动页面执行。安全边界应在应用侧:输入无论来自远端模型、端侧模型、RAG 文档还是工具结果,都先标为 untrusted_content,不能直接拼接进模板、脚本、Native bridge 参数或可信页面。
system instruction 可以约束模型的行为倾向,却不是输出安全证明。提示词可能被用户输入、检索内容、上下文截断或模型误差影响;即使模型按要求只返回 Markdown,Markdown 解析器仍可能支持原始 HTML、自动链接或扩展语法。应用要以实际解析树和目标渲染能力做决定,不使用“模型已经被告知不要输出脚本”替代结构校验。
内容信任和业务授权也应分开。文本中出现“打开设置”“上传文件”“继续支付”或某个深链,不表示用户授权执行这些动作。当前页面只讨论展示层的标签、脚本、文件、bridge 与外跳边界,不复制模型生成深链动作的完整授权方案。涉及账户、支付、文件或控制台的操作,应进入独立的宿主业务流程,而不是由渲染内容直接触发。
| 输入来源 | 初始信任 | 允许用途 | 禁止捷径 |
|---|---|---|---|
| 模型回复 | 不可信内容 | 解析和展示 | 直接执行 HTML 或脚本 |
| RAG 片段 | 不可信引用 | 带来源展示 | 覆盖宿主策略 |
| 工具结果 | 结构化但受限 | 按 schema 呈现 | 拼接成 bridge 调用 |
| 服务端模板 | 版本化资源 | 受控布局 | 混入未清洗用户文本 |
| 用户点击 | 明确交互信号 | 进入二次决策 | 等同业务授权 |
| 宿主命令 | 高权限动作 | 独立校验执行 | 从 Markdown 自动生成 |
优先解析 Markdown AST,而不是替换几个字符串
基于正则删除 script 或 javascript 字样很难覆盖嵌套节点、编码变化、属性组合和解析器扩展。更清晰的做法是使用固定版本的 Markdown 解析器生成 AST,再按节点类型重建输出。allowlist 只保留段落、标题、强调、列表、引用、受控代码块和业务确实需要的表格;原始 HTML、iframe、object、embed、表单、样式和未知扩展默认拒绝。重建意味着不复用原始标签字符串。
AST 节点的属性也要逐项重建。链接只接受明确 scheme 和有限长度,图片可选择完全禁用、经代理加载或只显示占位;代码块按纯文本转义,不成为可执行脚本。标题层级、列表深度、节点总数、文本长度、表格尺寸和嵌套深度应有资源上限,解析失败时显示纯文本或可解释错误,不能为了“尽量展示”切换到未清洗 HTML。
解析策略要绑定 parserVersion、policyVersion 和 contentDigest。缓存只保存重建后的 AST 或受控视图模型,并在策略版本变化时失效;不能缓存一份旧 HTML,在新版本中跳过清洗直接加载。摘要用于识别同一输入和回执,不证明内容安全。第三方 Markdown 扩展升级后,要重新检查其节点、属性与 URL 行为,不能假设库的小版本更新不会改变解析面。
| 节点类型 | 默认动作 | 保留条件 | 输出形式 |
|---|---|---|---|
| 段落与文本 | 保留 | 长度与字符检查 | 原生文本节点 |
| 标题与列表 | 保留 | 层级和深度受限 | 受控布局节点 |
| 代码块 | 文本化 | 语言标签 allowlist | 不可执行等宽文本 |
| 链接 | 重建 | scheme 与点击确认 | 宿主点击事件 |
| 图片 | 禁用或代理 | 来源和隐私策略明确 | 占位或受控加载 |
| 原始 HTML | 拒绝 | 无默认例外 | 安全错误提示 |
| 未知扩展 | 拒绝 | 策略升级后评审 | 不渲染 |
原生渲染与 WebView 需要不同的故障边界
对于标题、段落、列表、引用、代码和表格,原生组件通常更容易把内容限制在显示语义:链接点击由宿主接管,代码块只作为文本,节点不能自行访问文件或调用 JavaScript。原生渲染并非自动安全,解析器、图片加载器、富文本 span 和点击处理仍需审查;它的优势是能力面可以按组件枚举,而不是继承整个浏览上下文。
当业务确实需要复杂排版而采用 WebView,应为 AI 内容创建独立实例和独立配置,不能与登录、支付、账号中心或其他可信网页共享历史、bridge、cookie 或可复用页面状态。加载前把清洗后的内容和 policyVersion 绑定,页面导航交由宿主决策。渲染失败时回退为纯文本,不切换到一个权限更宽的共用 WebView。
选择哪种渲染器要记录功能需求,而不是以实现速度决定。若唯一需求是展示 Markdown,启用 JavaScript、DOM 注入、文件选择、下载或 bridge 缺少业务依据。若某个可视化组件必须运行脚本,应把它拆成经过签名和版本控制的宿主资源,模型只提供结构化数据;模型生成的脚本仍不进入执行通道。具体架构需结合目标 App 验证,本文不宣称某个实现已经通过。
| 能力 | 原生受控组件 | 隔离 WebView | 决策要求 |
|---|---|---|---|
| 文本排版 | 按节点实现 | HTML/CSS 呈现 | 优先最小能力 |
| 脚本 | 无执行语义 | 可配置但默认关闭 | 模型内容禁止开启 |
| 链接 | 宿主点击回调 | 导航拦截 | 二次确认和 allowlist |
| 文件访问 | 由业务显式读取 | 必须明确关闭 | 无展示需求即禁用 |
| Native bridge | 普通组件回调 | 高风险能力面 | AI 内容实例不注册 |
| 故障回退 | 纯文本或节点错误 | 纯文本页面 | 不得扩大权限 |
WebView 配置要按最小能力重新建立
Android WebView 文档说明 JavaScript、页面导航与宿主交互需要由应用配置。AI 内容实例应从最小能力开始:JavaScript 关闭,文件访问和 content 访问关闭,不允许从文件上下文扩大网络或本地资源访问,不注册 addJavascriptInterface,也不创建不必要的 message channel。只关闭一个开关不足以形成边界,设置、页面来源、导航拦截和宿主回调要一起审查。
WebView 生命周期也要隔离。账号切换、页面关闭或 policyVersion 变化后,停止加载、清空该内容实例的待处理导航与临时状态,并销毁与 AI 内容绑定的 bridge 空集和回调。不要把一个已经注册敏感 bridge 的 WebView 先显示可信页面,再复用来展示模型 HTML;清空页面内容不等于撤销所有宿主能力和历史状态。
如果业务必须为受控宿主资源启用 JavaScript,应把资源身份、脚本版本和数据通道与模型文本分开。模型输出只进入经过编码的文本节点或结构化数据,不拼接到 script、style、事件属性或 URL。启用例外需要威胁模型、代码审查和运行回执,不能作为通用 Markdown 配置。本文不提供可执行注入样例,也不推断任何具体页面可被利用。
| 设置面 | 默认值 | 允许例外 | 验证点 |
|---|---|---|---|
| JavaScript | 关闭 | 受控宿主资源另行评审 | 模型内容不能改变 |
| 文件访问 | 关闭 | 无默认例外 | 本地 URL 不可读取 |
| content 访问 | 关闭 | 明确业务通道 | 授权与 URI 范围 |
| Native bridge | 不注册 | AI 内容实例无例外 | 接口表为空 |
| 导航 | 宿主拦截 | 允许 host 与 scheme | 每次点击决策 |
| 实例复用 | 不与可信页共用 | 无默认例外 | cookie 与回调隔离 |
Bridge、frame 与 origin 不能靠页面看起来可信
Android WebView native bridge risks 指出 addJavascriptInterface 和 message channel 可能缺少充分的 frame 或 origin 隔离,并把宿主能力暴露给页面脚本。对 AI 生成内容,最可靠的默认值是不注册 bridge。即使主文档来自受控模板,模型输出中嵌入的 frame、导航或链接也不应获得宿主接口;页面视觉上显示自有品牌,不能证明执行上下文和 frame 来源可信。
若历史架构无法立即移除 bridge,迁移期间至少把 AI 内容放进不带 bridge 的独立实例,并让敏感操作回到宿主原生界面。不要尝试在模型文本中约定一个秘密函数名,或通过提示词要求模型不调用接口,这些都不是权限控制。bridge 方法的参数也不应直接接收模型构造的 JSON、文件路径、命令名称或 URL 后执行。
message channel 同样需要明确对端与消息 schema。能发送字符串不表示可以调用宿主动作;接收端先验证 channel 所属页面、消息类型、字段范围、用户状态和业务权限,再进入独立控制器。当前文章只给出展示层的禁用与迁移原则,复杂 bridge 认证需要单独设计。没有目标页面和运行证据时,不能写成风险已被阻断或接口已安全。
| 需求 | 危险捷径 | 推荐边界 | 用户体验 |
|---|---|---|---|
| 复制文本 | 页面调用通用 bridge | 宿主选择菜单 | 用户明确点击 |
| 打开链接 | 脚本直接导航 | 宿主拦截并确认 | 显示目标域名 |
| 上传文件 | bridge 接收路径 | 原生文件选择流程 | 展示选择范围 |
| 业务操作 | 模型生成命令 | 原生权限控制器 | 再次确认 |
| 消息通信 | 任意字符串通道 | 固定 schema 与来源校验 | 失败可解释 |
| 复杂组件 | 在回复中执行脚本 | 签名宿主资源加数据输入 | 能力独立说明 |
链接、图片和文件要通过独立决策器
Markdown 链接至少拆成显示文本、原始 URL、解析后 scheme、host、端口、路径类别和动作类型。默认只允许明确的 https 浏览链接,并在离开应用前展示目标;未知 scheme、本地文件、content URI、脚本 scheme、数据 URI 和带业务命令的自定义 scheme 进入拒绝或专用流程。规范化应使用平台 URL 解析器,不依赖字符串前缀判断。
Android WebView unsafe file inclusion 提醒文件访问、JavaScript 与不可信内容组合会扩大本地文件和脚本风险。关闭文件访问后,仍要限制导航、下载、重定向和资源加载,因为网络 URL 也可能离开预期范围。AI 回复中的图片可以默认显示占位,由用户选择是否加载;若使用图片代理,要明确缓存、日志、账号域和删除策略,不能把原 URL 静默交给第三方 SDK。
点击回执只记录 policyVersion、决策类别、scheme 类别、目标域名摘要和用户是否确认,不保存完整查询参数、对话正文或访问令牌。重定向后的最终目标仍需策略检查,不能只验证首个 URL。下载、文件选择、深链和支付等动作不属于普通浏览链接,必须进入各自业务门禁;本文不为这些相邻意图提供第二套答案。
| 对象 | 默认策略 | 允许条件 | 拒绝或转交 |
|---|---|---|---|
| https 链接 | 用户确认后外跳 | host 与动作允许 | 未知目标拒绝 |
| 自定义 scheme | 不直接执行 | 专用业务控制器 | 普通渲染层拒绝 |
| file URL | 拒绝 | 无展示例外 | 不交给 WebView |
| content URI | 拒绝 | 原生授权流程 | 不得从文本触发 |
| 远端图片 | 占位 | 用户选择或受控代理 | 不静默加载 |
| 下载 | 不自动开始 | 独立下载策略 | 普通链接流程转交 |
隐私声明和发布证据要覆盖渲染旁路
AI 回复渲染可能触发图片请求、链接预取、WebView 网络、分析事件、崩溃日志和第三方 SDK。Android Data safety 要求开发者了解应用与第三方库实际收集和共享的数据。发布前应把这些旁路与声明、隐私说明和 SDK 配置逐项对照。声明完整不代表技术隔离已经生效,声明缺少也不能单独证明发生了发送,两者需要不同证据。
NIST SSDF 提供来源、构建、验证、变更和供应链风险管理的组织方向。渲染策略应绑定 Markdown 解析器版本、allowlist、WebView 设置、URL 决策器、图片加载器、测试夹具和代码审查。任何解析器、WebView 支持库或 SDK 更新后,都重新执行未知节点、原始 HTML、脚本开关、文件访问、bridge 空表、重定向和缓存失效门禁。
运行验证使用公开安全夹具覆盖正常排版、未知节点、超深嵌套、外部链接、图片、原始 HTML、失败回退和策略升级,不使用客户会话或可执行攻击链。结果区分静态配置、设备观察和仍未验证的边界。SEO、GEO、流量、排名和客户效果没有真实数据时保持未接入,不能由审计通过、文章长度或模型复核推断。
- 渲染数据流覆盖 WebView、图片、链接预取、日志与第三方 SDK
- 解析器和 allowlist 版本与发布候选绑定
- AI 内容 WebView 的脚本、文件访问和 bridge 空表有运行回执
- 正常内容、拒绝内容、失败回退和策略升级均有受控夹具
- Data safety 与 SDK 声明按实际网络和日志行为复核
- 无目标证据的防护、兼容、性能和运营结论保持未证实
用策略清单拒绝高权限 WebView 配置
下面的 Python 示例读取一个公开安全的渲染策略 JSON。它要求 mode 为 ast_allowlist,rawHtml、javaScript、fileAccess、contentAccess、nativeBridge、downloads 和 automaticNavigation 全部关闭;allowedTags 必须是受控集合的非空子集,allowedSchemes 只能包含 https,externalConfirmation 必须开启。任何缺失、扩权或未知字段都产生非零退出。
示例检查的是配置声明,不解析真实 Markdown,也不能证明 WebView 设置已按清单应用、重定向受到拦截或第三方组件没有旁路。生产门禁还要把清洗后的 AST、WebView 实例、URL 决策、图片加载、网络观察和用户确认回执关联到同一 policyVersion。代码不包含真实域名、包名、凭据、服务器信息、客户数据或攻击载荷。
可同时参阅本站关于模型生成深链动作边界与 AI 输出日志隐私的技术说明,分别处理业务动作授权和结果记录;当前页面只回答渲染前隔离。准备具体 App 的输出展示评估时,可整理脱敏节点清单、WebView 设置、bridge 表、URL 规则、SDK 清单和测试夹具,再从御盾中央平台提交申请;页面框架会统一提供本站内链和中央行动按钮。
- 策略字段与已审查 schema 完全一致,不接受未知扩权字段
- 只允许 AST allowlist,raw HTML 默认拒绝
- JavaScript、文件、content、bridge、下载和自动导航均关闭
- 普通浏览链接只接受 https 并要求用户确认
- 标签集合是有限 allowlist 的唯一非空子集
- 结构通过后仍验证真实解析器、WebView、网络和点击回执
from pathlib import Path
import json
import sys
if len(sys.argv) != 2:
raise SystemExit("usage: rendering_policy_gate.py policy.json")
path = Path(sys.argv[1])
if not path.is_file():
raise SystemExit("policy file is missing")
try:
policy = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise SystemExit("policy is not valid JSON") from exc
if not isinstance(policy, dict):
raise SystemExit("policy must be an object")
required_keys = {
"mode",
"rawHtml",
"javaScript",
"fileAccess",
"contentAccess",
"nativeBridge",
"downloads",
"automaticNavigation",
"externalConfirmation",
"allowedTags",
"allowedSchemes",
"policyVersion",
}
if set(policy) != required_keys:
raise SystemExit("policy fields do not match the reviewed schema")
if policy["mode"] != "ast_allowlist":
raise SystemExit("rendering must use an AST allowlist")
for field in (
"rawHtml",
"javaScript",
"fileAccess",
"contentAccess",
"nativeBridge",
"downloads",
"automaticNavigation",
):
if policy[field] is not False:
raise SystemExit(f"{field} must be disabled")
if policy["externalConfirmation"] is not True:
raise SystemExit("external navigation requires confirmation")
safe_tags = {"paragraph", "heading", "emphasis", "list", "quote", "code", "table", "link"}
allowed_tags = policy["allowedTags"]
if not isinstance(allowed_tags, list) or not allowed_tags:
raise SystemExit("allowedTags must be a nonempty list")
if len(allowed_tags) != len(set(allowed_tags)):
raise SystemExit("allowedTags contains duplicates")
if not set(allowed_tags).issubset(safe_tags):
raise SystemExit("allowedTags contains an unreviewed node")
if policy["allowedSchemes"] != ["https"]:
raise SystemExit("only https links may enter the ordinary browser flow")
version = policy["policyVersion"]
if not isinstance(version, int) or version < 1:
raise SystemExit("policyVersion must be a positive integer")
print(f"validated_policy={version} allowed_tags={len(allowed_tags)}")事实依据与适用边界
以下内容区分官方事实、本文工程判断和不能外推的范围,避免把设计建议写成未经验证的产品结论。
| 本文判断 | 事实或工程依据 | 适用限制 |
|---|---|---|
| WebView 的 JavaScript、导航和宿主交互需要由应用明确配置和控制。 | Build Android WebView apps 说明 WebView 内容加载、JavaScript 和页面与应用交互的基础方式。 | 页面能够展示不代表 bridge、缓存、origin、文件访问和导航策略已经正确。 |
| WebView Native bridge 可能因 frame 或 origin 隔离不足而暴露宿主能力。 | Android WebView native bridge risks 描述 addJavascriptInterface 和 message channel 的风险与缓解方向。 | 风险指南不说明某个业务 bridge 已可被利用,也不能替代目标页面运行验证。 |
| WebView 文件访问、JavaScript 和不可信内容组合会扩大本地文件与脚本风险。 | Android WebView unsafe file inclusion 说明不安全文件访问配置及相关风险和缓解措施。 | 关闭文件访问仍需验证 URL allowlist、重定向、TLS、图片加载和 bridge 权限。 |
| system instruction 会影响模型行为,但不能作为输出执行和工具权限的安全边界。 | Firebase AI Logic system instructions 说明系统指令随请求提供并影响模型回复的方式。 | 指令不能完全阻止泄露、越权或不符合预期的输出,应用仍需独立校验渲染内容。 |
| Android 数据声明需要覆盖应用代码与第三方 SDK 实际收集或共享的数据。 | Declare Android app data use 说明开发者需根据应用和第三方库的数据行为完成 Data safety 信息。 | 商店声明不是运行时隔离、日志脱敏、链接确认或 WebView 配置控制。 |
| 渲染策略的发布过程应保留来源、构建、验证和变更证据。 | NIST SP 800-218 SSDF 给出组织级安全软件开发与供应链风险管理实践。 | SSDF 不定义具体 Markdown allowlist,也不证明御盾或任何 App 具备特定防护能力。 |
| AI Markdown 应先解析为受控 AST,再按允许节点重建视图。 | 工程判断:结构化重建可以把标签、属性、URL 和资源上限变成可审计门禁,避免依赖字符串替换。 | 解析器本身、扩展语法和重建实现仍需版本绑定与目标 App 测试,静态策略不能证明无旁路。 |
| AI 内容 WebView 应与带登录、支付或 Native bridge 的可信 WebView 分开。 | 工程判断:独立实例能把脚本、文件、cookie、bridge 与导航能力限制在单一展示用途。 | 隔离方案依赖目标架构;是否真正分离要检查实例、配置、缓存、回调与运行网络回执。 |
工程常见问题
模型只输出 Markdown,是否就不需要清洗?
仍需清洗。Markdown 解析器可能支持原始 HTML、自动链接和扩展节点,应解析为 AST 后按节点与属性 allowlist 重建。
用正则删除 script 标签能否解决危险 HTML?
不能作为完整边界。编码、嵌套、属性和解析器差异会让字符串替换失效;应拒绝原始 HTML,并对解析树执行结构化策略。
关闭 JavaScript 后,WebView 是否已经安全?
还没有。还需关闭文件与 content 访问、不注册 bridge、限制导航和图片、隔离实例,并验证缓存、重定向和用户点击。
system instruction 要求模型不要输出链接,能否替代 URL allowlist?
不能。模型输出仍可能偏离指令,应用必须解析实际 URL,检查 scheme、host、动作类型并在外跳前取得用户确认。
为什么不复用应用已有的登录 WebView 展示 AI 回复?
已有实例可能持有 cookie、历史状态、JavaScript 或 Native bridge。AI 内容应使用最小能力的独立渲染实例,避免继承高权限上下文。
策略 JSON 校验通过是否代表渲染链已经安全?
不代表。它只检查声明,还需验证真实解析器、WebView 设置、URL 重定向、图片、缓存、SDK 和设备运行回执。