先看结论与判断条件
- 模型输出、system instructions 和工具声明都属于行为输入,不是服务端授权证据,任何调用都要经过独立策略判断。
- 工具 schema 应固定版本、拒绝未知字段、约束参数类型和业务语义,并把解析后的规范对象送入授权层。
- 用户确认必须绑定具体动作、关键参数、目标资源和有效上下文,通用的“我同意”不能覆盖后续任意调用。
- 资源服务器要独立验证令牌 issuer、audience、expiry、client 与 scope,令牌可解析不等于当前业务动作获准。
- DPoP 能降低被盗访问令牌的重放风险,但不能证明设备可信,也不能代替资源所有权和服务端业务规则。
- 幂等键、资源版本和审计记录共同处理重复提交与竞态,移动端抗篡改仅作为纵深防御,不能承担当局授权。
先把模型建议与真实执行拆成两条链
移动 AI 应用常把搜索、下单、发消息、导出文件或修改配置封装成工具。模型根据对话生成工具名和参数,看起来像一个完整请求,但这只是候选动作。模型可能误解上下文,也可能受不可信内容影响,还可能生成超出当前用户权限的参数。服务端若把结构正确的工具调用直接执行,就等于把概率输出提升成业务授权。
Firebase AI Logic system instructions 说明 system instructions 会随请求影响模型行为,但应用仍要控制输入、输出和工具权限。系统指令适合表达角色、语气和任务约束,却不是不可绕过的安全边界。工程上应把模型阶段限制为产生意图,把执行阶段放到确定性的验证器中;验证器只接受白名单工具、规范参数、有效令牌和当前业务状态。
一条可审计链至少包含模型建议、schema 解析、用户确认、令牌校验、资源授权、幂等控制、真实执行和结果回写。每一步都保存独立状态,失败时返回明确但不过度泄露的错误。这样团队可以判断问题出在模型选错工具、客户端组装错误、令牌不足还是资源规则拒绝,而不是用一句“AI 调用失败”掩盖安全判断。
| 阶段 | 输入 | 能证明什么 | 不能证明什么 |
|---|---|---|---|
| 模型建议 | 对话与工具描述 | 模型选择了候选动作 | 用户有权执行 |
| Schema 解析 | 工具名与参数 | 结构符合当前契约 | 业务状态允许 |
| 用户确认 | 动作摘要与关键参数 | 用户确认了特定展示内容 | 令牌和资源权限有效 |
| 令牌校验 | access token 与证明 | 调用方满足协议策略 | 目标资源属于调用方 |
| 业务授权 | 主体、资源、动作与状态 | 当前请求可否执行 | 模型输出必然正确 |
把工具 Schema 当作版本化安全契约
Firebase server prompt template syntax 展示服务端 Prompt 模板可同时定义模型配置、system instructions、输入 schema、工具声明和模板正文。这些字段会共同影响模型看到什么工具以及如何构造参数,因此应作为同一版本的配置资产审查。模板处于预览或发生版本变化时,更要保存发布版本、字段差异和回滚点,不能假设语法不变。
工具 schema 的第一道门是拒绝模糊输入。工具名使用稳定标识,参数对象禁止未知字段,字符串区分标识符与自由文本,枚举只接受明确成员,数量和时间等值要按业务语义解析。schema 验证后再生成规范对象,例如统一时区、去除重复项并解析资源标识。授权判断只读取规范对象,避免同一参数在展示、签名和执行环节产生不同解释。
schema 通过仍不等于允许执行。一个合法的 transferFunds 参数对象可能指向不属于当前用户的账户,一个合法的 exportDocument 请求可能读取受限制空间。工程判断是让 schema 回答“请求是否可理解”,让授权层回答“主体是否能在当前状态对该资源执行动作”。两种结果分别记录,禁止用解析成功覆盖权限拒绝。
| 字段策略 | 处理方式 | 拒绝示例 | 后续判断 |
|---|---|---|---|
| 工具名白名单 | 匹配版本化注册表 | 未知或停用工具 | 进入该工具专属策略 |
| 拒绝未知字段 | 只保留契约声明键 | 附加管理员或 owner 字段 | 防止参数走私 |
| 强类型与枚举 | 先解析再规范化 | 字符串冒充数量或状态 | 生成确定性对象 |
| 资源标识 | 按固定格式解析 | 混合名称与内部标识 | 查询真实资源属性 |
| Schema 版本 | 请求绑定注册表版本 | 旧客户端调用已撤销字段 | 选择迁移或拒绝 |
用户确认要绑定动作、参数与当前上下文
高影响工具通常需要用户确认,但确认页不能只显示模型生成的自然语言摘要。服务端应先规范化请求,再把工具标识、目标资源、关键参数、费用或不可逆后果等确认字段返回客户端。客户端展示这些确定字段,用户确认后提交与原请求绑定的确认标识。若模型在确认前后改变参数,原确认不应继续有效。
确认记录还要区分发起用户、当前会话和业务资源。共用设备、账号切换、应用恢复和并发窗口都可能让旧确认落到新上下文。工程判断是让确认标识绑定主体、规范请求摘要、资源版本和一次执行目的,并由服务端验证当前状态。这里不提供统一有效期,因为动作风险、交互时长和业务补偿能力各不相同。
低风险工具也不能把确认省略误解为授权省略。读取公开帮助内容可以采用较轻交互,但仍需 schema、令牌、资源和频率策略;发送消息、修改配置或产生费用的动作则需要更清晰的确认。产品可以按风险分级决定交互强度,服务端的业务授权始终保留,不能让客户端界面是否弹窗决定最终权限。
| 对象 | 绑定内容 | 失配风险 | 处理方式 |
|---|---|---|---|
| 主体 | 当前账号与客户端会话 | 账号切换后复用 | 重新确认 |
| 动作 | 稳定工具标识和版本 | 同一文案对应不同工具 | 按工具策略核验 |
| 参数 | 规范请求摘要 | 确认后参数被替换 | 摘要不一致即拒绝 |
| 资源 | 资源标识与版本 | 资源状态已改变 | 重新读取并授权 |
| 后果 | 费用、外发或不可逆说明 | 用户未看到关键影响 | 阻断高影响执行 |
资源服务器独立验证 Token 与 Scope
RFC 9068 JWT access token profile 说明 JWT access token 可携带 issuer、audience、expiry、scope 与 client 标识供资源服务器验证。工具服务不应只检查签名后读取 user id,还要确认令牌由认可 issuer 签发、audience 包含当前资源服务器、尚未过期、client 符合调用路径,并且 scope 覆盖当前工具动作。解析出声明只是开始,策略匹配才产生协议层结论。
scope 应表达可管理的协议权限,不能把全部业务状态塞进字符串。比如 document.write 可以说明令牌允许请求写文档,但文档是否属于当前组织、是否被锁定、当前用户在该空间是什么角色,仍由业务数据库和策略引擎判断。反过来,资源属于用户也不能弥补缺失 scope。两层都通过,调用才进入执行阶段。
RFC 9700 OAuth Security BCP 汇总重定向、令牌重放、授权码注入和不安全 grant 等部署风险。移动 AI 工具链应避免把长期高权限令牌交给模型上下文或日志,也不能让模型决定 OAuth 参数。令牌获取、刷新、存储和发送由受控客户端与协议组件承担,模型只看到执行所需的最小业务结果。安全 BCP 不决定具体商业角色,业务权限仍需项目策略定义。
| 检查 | 数据来源 | 失败含义 | 通过后仍需检查 |
|---|---|---|---|
| issuer | 令牌声明与信任配置 | 签发方不被接受 | audience、有效期与签名策略 |
| audience | 令牌声明与服务标识 | 令牌不是发给当前服务 | scope 与业务资源 |
| expiry | 令牌时间声明与服务时钟 | 令牌不在有效窗口 | 重放约束与会话状态 |
| scope | 令牌声明与工具策略 | 协议权限不足 | 资源所有权和业务角色 |
| client | 令牌声明与调用路径 | 客户端不符合策略 | 主体、资源和动作关系 |
DPoP 降低令牌重放,但不证明设备可信
RFC 9449 OAuth DPoP 定义用客户端持有密钥生成证明,并把访问令牌约束到对应密钥的机制。资源服务器验证 DPoP proof 与请求方法、目标地址、时间和令牌关联后,可以降低被盗 bearer token 被其他持有者直接重放的风险。对于能触发真实业务动作的 AI 工具,这是一项有价值的协议层约束。
DPoP 不是设备证明。持有密钥的客户端仍可能运行在被控制的环境,应用逻辑也可能被修改;密钥绑定更不能回答用户是否拥有目标资源。服务端仍需验证 scope、主体、资源、业务状态和确认记录,并采用合适的令牌有效期与撤销策略。把 DPoP 验证成功写成“设备可信”会扩大规范结论。
实现时还要处理 proof 唯一标识、时间偏差、服务端 nonce、目标地址规范化和代理转发边界。若网关负责校验,后端必须获得不可伪造且边界清晰的校验结果,不能信任客户端自报。工程判断是把 DPoP 结果作为授权输入之一,并保存失败类别,但日志中不记录完整访问令牌、私钥或可直接重放的敏感材料。
- DPoP proof 与当前 HTTP 方法和目标地址匹配
- 访问令牌确认绑定到证明密钥
- 唯一标识和时间策略处理重复证明
- 网关到后端的校验结果具有可信边界
- DPoP 通过后继续检查 scope 与资源授权
- 日志不保存令牌、私钥或完整证明载荷
资源所有权、业务状态与幂等必须一起判断
工具服务拿到有效主体后,应从服务端数据读取目标资源,而不是接受客户端传入的 owner 字段。策略输入至少包含主体、组织、资源真实所有者、角色、动作、资源状态和工具版本。跨租户资源要先确定租户边界,再判断角色权限;软删除、冻结、审批中或版本冲突的资源,即使所有者相同也可能不能执行当前动作。
AI 工具调用容易因网络恢复、流式响应中断、客户端重试或模型重复建议而多次提交。幂等键应由受控应用生成并绑定主体、工具和规范请求摘要,服务端保存首次结果。相同键配相同摘要可以返回原结果,相同键配不同摘要必须拒绝。幂等控制避免重复副作用,但不能把第一次未授权的请求变成已授权。
竞态还要求资源版本参与决策。用户确认时看到的文档、订单或配置,执行前可能已被其他流程修改。服务端可比较确认记录中的资源版本和当前版本,失配时重新展示或要求业务冲突处理。工程判断是让授权和写入位于同一受控事务边界,避免授权检查后到实际更新前状态发生变化。
| 输入 | 可信来源 | 典型拒绝原因 | 审计记录 |
|---|---|---|---|
| 主体与组织 | 已验证令牌和服务端会话 | 主体不在目标租户 | 主体、client 与组织标识 |
| 资源所有者 | 服务端资源存储 | 客户端声明与真实归属不同 | 资源标识和真实 owner |
| 动作与参数 | 规范化工具请求 | 动作超出角色或参数边界 | 工具版本与请求摘要 |
| 资源状态 | 当前数据库记录 | 冻结、删除或版本冲突 | 状态和版本快照 |
| 幂等键 | 受控客户端与服务端记录 | 同键对应不同请求 | 首次结果与重放判定 |
移动端防护只做纵深防御,不接管授权
OWASP MASVS-RESILIENCE 把抗篡改与抗逆向放在移动应用纵深防御中。对 AI 工具调用而言,客户端保护可以增加修改工具注册表、替换确认界面或窃取集成逻辑的成本,也有助于保护发布候选的完整性。但控制目录本身不证明某个候选包达到任何强度,更不能让服务端跳过权限检查。
所有安全关键字段都应假设客户端可能被观察或修改。owner、价格、角色、scope、确认状态和幂等结果不能以客户端自报为准;客户端传来的 schema 版本也要在服务端注册表中解析。移动端可以持有短期凭据和生成协议证明,却不应包含能够授予任意业务权限的静态秘密或本地管理员开关。
发布验收需要把客户端与服务端分别留证。客户端侧记录候选摘要、签名身份、工具注册表版本和关键交互回归;服务端侧记录令牌策略、schema 注册表、资源授权测试、幂等与审计结果。项目证据尚未接入时,只能说明设计门禁和待验证项,不能声称攻击阻断、兼容通过或统一性能结果。
| 控制 | 客户端职责 | 服务端职责 | 错误做法 |
|---|---|---|---|
| 工具注册 | 展示受支持动作 | 维护权威 schema 与版本 | 客户端声明新工具即接受 |
| 用户确认 | 展示规范动作和后果 | 绑定确认与请求摘要 | 只信任本地确认布尔值 |
| 令牌 | 安全获取和发送 | 验证签名、声明与 scope | 把高权限令牌放进模型上下文 |
| 资源授权 | 提交资源标识 | 读取真实归属与状态 | 接受客户端 owner 字段 |
| 应用防护 | 提高篡改与分析成本 | 始终执行独立授权 | 加固后关闭服务端校验 |
用只读校验器固定发布前的授权输入
下面的 Python 示例读取策略文件和待审计调用文件,只做离线校验,不访问业务系统。策略定义工具允许字段、必需 scope 和资源所有者;调用文件包含工具名、参数、令牌 scope、主体、资源 owner 与幂等键。脚本拒绝未知工具、字段走私、scope 不足、owner 失配和无效幂等键,并输出规范摘要供审计。
示例把工具 schema、scope、owner 和幂等键放在同一检查器里,是为了展示数据如何汇合,不代表生产系统应依赖一个本地 JSON 文件。真实服务要从可信令牌验证器、schema 注册表和资源数据库获得这些值,并在写入事务中重新判断。客户端提交的 tokenScopes 或 resourceOwner 不能直接成为可信事实。
发布门禁应同时覆盖允许路径和拒绝路径,包括未知字段、缺 scope、跨资源、相同幂等键不同摘要、确认过期和资源版本变化。准备移动 AI 应用加固评估时,可整理真实候选、工具注册表、服务端授权策略、令牌架构、关键动作清单和回归范围,再通过御盾中央平台提交申请。评估结论必须绑定实际材料,不能用模型提示词替代授权证据。
- 模型输出只生成候选动作,不生成授权结论
- 服务端按版本化 schema 拒绝未知字段
- 令牌 issuer、audience、expiry、client 与 scope 分别验证
- 资源 owner 和状态从服务端可信存储读取
- 确认记录与规范请求摘要及资源版本绑定
- 幂等键相同但请求摘要不同必须拒绝
- 移动端防护不替代任何服务端授权门禁
from pathlib import Path
import hashlib
import json
import re
import sys
if len(sys.argv) != 3:
raise SystemExit(2)
policy_path = Path(sys.argv[1])
call_path = Path(sys.argv[2])
if not policy_path.is_file() or not call_path.is_file():
raise SystemExit(2)
policy = json.loads(policy_path.read_text(encoding="utf-8"))
call = json.loads(call_path.read_text(encoding="utf-8"))
tool_name = str(call.get("tool", ""))
tool_policy = policy.get("tools", {}).get(tool_name)
if not isinstance(tool_policy, dict):
raise SystemExit(2)
arguments = call.get("arguments", {})
if not isinstance(arguments, dict):
raise SystemExit(2)
allowed_fields = set(tool_policy.get("allowedFields", []))
required_fields = set(tool_policy.get("requiredFields", []))
argument_fields = set(arguments)
if not argument_fields.issubset(allowed_fields) or not required_fields.issubset(argument_fields):
raise SystemExit(2)
token_scopes = set(call.get("tokenScopes", []))
required_scopes = set(tool_policy.get("requiredScopes", []))
if not required_scopes.issubset(token_scopes):
raise SystemExit(2)
subject = str(call.get("subject", ""))
resource_owner = str(call.get("resourceOwner", ""))
if not subject or subject != resource_owner:
raise SystemExit(2)
idempotency_key = str(call.get("idempotencyKey", ""))
if re.fullmatch(r"[A-Za-z0-9._:]{16,128}", idempotency_key) is None:
raise SystemExit(2)
canonical = json.dumps({"tool": tool_name, "arguments": arguments, "subject": subject}, sort_keys=True, separators=(",", ":"))
request_digest = hashlib.sha256(canonical.encode("utf-8")).hexdigest()
result = {"status": "eligible-for-server-authorization", "tool": tool_name, "requestDigest": request_digest, "idempotencyKey": idempotency_key}
print(json.dumps(result, ensure_ascii=False, indent=2))事实依据与适用边界
以下内容区分官方事实、本文工程判断和不能外推的范围,避免把设计建议写成未经验证的产品结论。
| 本文判断 | 事实或工程依据 | 适用限制 |
|---|---|---|
| 服务端 Prompt 模板可以组合模型配置、system instructions、输入 schema、工具声明和模板正文。 | Firebase server prompt template syntax 展示这些字段的模板语法与配置关系。 | 模板能力存在预览期限制,语法正确不保证模型稳定,也不替代客户端和服务端授权。 |
| system instructions 会影响模型行为,但应用仍需控制输入、输出和工具权限。 | Firebase AI Logic system instructions 说明系统指令的作用方式与应用侧控制责任。 | 系统指令不能完全阻止泄露、越权或意外输出,不能被当成授权边界。 |
| OAuth 部署需要处理令牌重放、授权码注入、重定向与不安全 grant 等风险。 | RFC 9700 OAuth Security BCP 汇总 OAuth 部署的安全建议与已知攻击面。 | 该 BCP 不定义具体 AI 工具的商业角色、资源所有权或动作权限。 |
| JWT access token 可携带 issuer、audience、expiry、scope 和 client 标识供资源服务器验证。 | RFC 9068 JWT access token profile 定义面向 OAuth 资源访问的 JWT 声明配置。 | JWT 格式不保证签发策略正确、令牌足够短命或业务资源已经授权。 |
| DPoP 可以把 access token 约束到客户端持有的密钥并降低被盗令牌重放风险。 | RFC 9449 OAuth DPoP 定义 proof 与访问令牌的发送者约束机制。 | DPoP 不证明设备可信,也不替代 scope、资源所有权、短有效期和业务授权。 |
| 移动端抗篡改与抗逆向属于纵深防御,不应承担服务端授权职责。 | OWASP MASVS-RESILIENCE 把相关控制放在移动应用韧性与纵深防御范围。 | 控制目录不证明某个候选包的防护强度,也不支持关闭服务端权限检查。 |
| 工具 schema 通过只说明请求可理解,不能说明主体有权执行动作。 | 工程判断:结构验证与主体、资源、动作、状态的授权判断回答不同问题。 | 具体角色、资源状态和确认规则必须由项目业务策略与真实数据确定。 |
| 幂等键应绑定主体、工具与规范请求摘要,相同键配不同请求必须拒绝。 | 工程判断:这种绑定能区分合法重试与参数被替换的重复提交。 | 幂等控制不授予权限,也不能替代事务、资源版本和业务补偿设计。 |
工程常见问题
工具调用已经符合 JSON Schema,为什么服务端还要鉴权?
Schema 只证明工具名和参数可以被理解,不证明令牌 scope 足够、资源属于当前主体、业务状态允许或用户确认仍有效。服务端必须用规范请求重新执行这些判断。
在 system instructions 中写明禁止越权,能否代替权限校验?
不能。system instructions 会影响模型行为,但不是不可绕过的安全边界。模型只能建议动作,真实工具服务仍要验证令牌、资源、动作和当前业务状态。
JWT 签名验证通过,是否就能执行 AI 工具?
不能。还要验证 issuer、audience、expiry、client 与 scope,再从服务端读取资源所有者、角色和状态。JWT 格式和有效签名都不能自动授予具体业务权限。
使用 DPoP 后还需要短期令牌和资源授权吗?
需要。DPoP 主要降低被盗 bearer token 被其他持有者重放的风险,不证明设备可信,也不说明调用者有权修改目标资源。
用户已经点击确认,为什么同一请求仍可能被拒绝?
确认可能与当前主体、参数或资源版本不一致,令牌也可能过期或 scope 不足。确认是授权输入之一,服务端要在执行时重新核对完整上下文。
移动 AI 工具链申请加固评估前要准备什么?
准备真实候选包、工具 schema 注册表、关键动作与确认流程、令牌架构、服务端资源授权规则、幂等策略和目标回归范围,再通过御盾中央平台提交申请。