先看结论与判断条件

  • 下载、真实性验证、完整性验证、兼容检查、运行时编译、活动切换和旧版清理必须是可独立失败的阶段。
  • 可信清单应绑定版本、过期时间、全部文件摘要、运行时要求和发布证据,不能只校验主模型文件。
  • 下载内容始终落在非活动临时目录,只有全部验证与预热通过后才生成不可变候选版本。
  • 活动版本宜用同一文件系统内的短小指针原子替换,推理请求先固定一次快照,不能边读边切换。
  • 失败回退应保留上一已知可用版本并限制重复尝试,避免服务端持续下发同一坏候选造成启动循环。
  • 旧版本清理与启用解耦,只有确认没有进程或请求持有旧快照后才进入容量回收。

把模型更新定义成状态机而不是一次下载

移动端更新模型的风险不在于文件是否能下载,而在于多个阶段可能只完成一半。网络中断会留下截断文件,应用被系统终止会打断编译,清单和关联资源可能来自不同发布批次,切换时另一个线程仍在加载旧版本。若代码只用“文件存在”判断可用,半成品就可能进入推理路径。工程判断是把更新明确拆成发现、授权、下载、验证、兼容检查、编译或准备、候选冻结、原子启用和清理,每个状态都有唯一入口与失败出口。

状态机需要持久化,但持久化的不是模糊布尔值。至少记录目标版本、清单摘要、临时目录、已完成文件、候选目录、失败阶段、失败类别和最后尝试时间。应用重启后只能从可证明的阶段恢复:已下载文件重新做摘要校验,编译结果重新确认与候选匹配,未完成的活动切换则读取当前指针而不是猜测。任何无法解释的状态都回到旧活动版本,并把临时内容隔离等待下一次受控重试。

用户触发、后台任务和推送通知可能同时启动更新,必须由单一协调器串行化同一模型族的激活。锁只保护本地临界区,不能代替版本策略;服务端仍要防止把较旧版本、过期清单或不兼容候选发给客户端。放行证据应显示每次状态转换的输入摘要和结果,而不是只记录“更新成功”,这样故障时才能区分网络、供应链、兼容、编译还是切换问题。

模型更新状态及其放行条件
状态允许写入的位置进入下一状态的条件失败动作
发现候选内存或受控元数据版本策略与清单入口可接受保留活动版本并延迟重试
下载中非活动临时目录全部声明文件传输完成保留可验证分片或隔离临时目录
已验证不可变候选目录签名、版本、过期与摘要全部通过候选标记拒绝且不可启用
准备运行候选专属编译或缓存目录编译、元数据与试加载通过丢弃准备结果并记录失败阶段
活动切换短小活动指针原子替换完成并可重新读取继续使用旧指针
稳定观察活动与上一版本并存真实业务探针和错误预算满足原子切回上一已知可用版本

可信清单必须先于模型文件进入决策

客户端不能从下载 URL 或文件名推断版本与可信度。The Update Framework specification 提供的更新思路包括验证角色签名、版本、过期时间和一致快照,用于处理回滚、冻结与元数据混搭风险。移动模型不一定直接采用完整 TUF 实现,但工程边界相同:先得到可验证的可信元数据,再依据元数据选择目标文件;不能先下载任意文件,最后只比较一个由同一不可信响应提供的摘要。

清单至少要绑定模型族、发布版本、目标平台、运行时要求、主模型及全部关联文件摘要、文件长度、兼容 schema、发布时间和过期时间。客户端还要保存已接受的最高版本或等价回滚状态,拒绝无授权的旧元数据。设备时间不可靠时,过期判断需要受控时间来源或服务端挑战,不能因为本地时间异常就永久绕过过期检查。任何签名或版本验证失败都应在下载之前停止。

一致性还涉及多文件发布。主模型、tokenizer、标签、归一化配置和业务规则必须来自同一候选清单,不能在缓存命中时随意混用上次文件。清单更新采用新的不可变版本目录,下载器按摘要寻址并在所有文件完成后冻结目录。工程判断是把“候选完整”定义为清单中每个必需成员都存在且摘要匹配,而不是主模型达到预期大小。

可信清单中应明确的最小约束
清单字段解决的问题客户端检查不能替代
版本与序列识别未授权回滚高于或等于本地允许下限业务授权和灰度策略
过期时间限制冻结旧元数据在可信时间边界内有效持续在线可用性
文件摘要与长度发现截断、替换和混搭逐文件流式计算并比较发布者身份验证
目标平台与运行时阻止明显不兼容候选平台、架构、运行时版本匹配真实设备试加载
关联文件集合保持 tokenizer 与模型一致必需成员完整且无未知替换业务输入输出回归
签名与角色确认元数据授权来源使用受控信任根验证声明内容本身必然正确

供应链证明绑定候选身份但不替代运行测试

SLSA Provenance v1.1 说明构建证明可以绑定产物主体、构建者、构建类型、外部参数和依赖材料。对移动模型发布,候选清单可引用模型文件摘要及对应 provenance,发布服务在下发前确认声明主体与实际候选一致。这样评审者能够回答模型由哪个受信构建流程产生、使用了哪些外部输入,而不是只看到一个无法追溯的二进制文件。

in-toto Attestation Statement v1 定义把产物摘要与有类型的声明负载放在同一 statement 中的通用结构。格式解决的是报告与产物如何绑定,不自动保证内容真实。客户端或发布服务仍需验证签名者、声明类型、策略字段和摘要,拒绝把另一个版本的测试报告附到当前模型。工程判断是移动端只接收最小化的放行结论与必要引用,完整供应链材料可由发布后台保存,但二者必须指向同一候选摘要。

provenance 与 attestation 不能证明目标设备上编译成功、输入输出语义正确或性能满足预算。构建来源可接受后,客户端还要执行平台兼容检查、运行时编译或解释器加载、元数据解析和代表性探针。项目没有真实构建证明时,应把来源状态标为未接入,继续做摘要与兼容门禁,但不能把普通 HTTPS 下载扩写成供应链验证已经完成。

发布证据与设备证据的职责分工
证据层回答的问题绑定对象明确边界
可信清单哪些文件和版本被授权发布清单签名与文件摘要不证明构建过程正确
SLSA provenance候选如何由构建流程产生产物主体与构建材料不证明设备运行语义
in-toto statement声明负载如何绑定产物subject 摘要与 predicate 类型格式不保证陈述真实
客户端兼容检查候选是否适配当前运行环境设备条件与候选清单静态检查不替代加载
运行时试加载编译、解析和基本入口是否成功设备、运行时与候选版本探针不代表完整业务回归
稳定观察真实请求是否满足错误预算活动版本与发布窗口短期成功不代表永久安全

全部文件先进入临时目录并逐项验证

下载目录与活动目录必须物理分离。每个候选使用不可猜测但可审计的临时目录,文件下载时使用临时扩展名,写入完成后刷新文件内容与目录元数据,再进行长度和摘要校验。客户端只接受可信清单列出的相对路径,拒绝绝对路径、父目录跳转、符号链接和重复成员,避免关联文件越过候选根目录或覆盖活动指针。

摘要应在流式写入或完成后从磁盘重新计算,而不是信任网络库返回的校验值。若支持分片续传,每片验证只能证明片段未损坏,最终仍要对完整文件计算清单指定摘要。失败文件保留与否取决于是否能够安全复用,任何没有明确摘要身份的残片都不能被下一次请求直接当成已完成。存储空间不足时先停止候选准备,不应删除当前活动版本来换取下载空间。

LiteRT model metadata 说明模型元数据可以携带输入输出描述、归一化参数、标签和关联文件。这个事实意味着更新校验不能只覆盖主模型;tokenizer、词表、标签、预处理参数和其他关联资产都可能改变推理语义。候选完整性表应逐项列出用途、摘要、长度和兼容版本,未知额外文件默认不进入运行目录,缺少必需成员则整个候选失败。

  • 下载目录与活动目录完全分离
  • 清单路径经过规范化且不能越过候选根目录
  • 每个文件从落盘内容重新计算摘要
  • 分片完成后仍执行完整文件摘要
  • 主模型和全部关联资产属于同一清单
  • 空间不足不会删除唯一可用活动版本

兼容检查和运行时准备必须在切换之前

Download and compile Core ML models 描述动态 Core ML 模型先下载到本地、编译,再从编译产物初始化的路径。工程上应把编译输出视为候选派生产物,而不是下载成功的附带结果。输入模型摘要、编译工具或系统版本、编译目录和初始化结果都要与候选版本绑定;只有从编译产物成功创建模型并通过受控探针,候选才进入可切换状态。

其他运行时也有各自兼容条件,包括算子集合、delegate、硬件架构、量化方式、输入 shape 和元数据 schema。静态清单可以提前排除已知不兼容组合,真实设备试加载负责捕捉运行时差异。探针应使用公开安全、固定且不包含用户数据的输入,检查输出结构、有限数值和必要业务不变量;它不能证明全部模型质量,却能阻止无法加载或接口明显变化的候选进入活动路径。

Manage Core AI model caching 说明模型缓存可能随源模型、OS 版本、存储压力和清理策略失效或重建。该语义不能外推到所有 Core ML 或 Android 运行时,但提示团队不要把已有缓存当成永久有效证据。候选启用记录应区分源模型、编译产物和运行时缓存,缓存缺失时能够安全重建,缓存命中时也要确认其来源版本与当前候选一致。

切换前的兼容与准备门禁
门禁输入通过条件失败处理
平台约束系统、架构、运行时版本满足清单声明范围不下载或隔离候选
文件完整性全部文件与关联资产长度和摘要逐项一致拒绝整个候选
运行时编译源模型与编译环境生成可识别编译产物保留旧活动版本
模型初始化候选运行产物实例创建且入口可调用记录运行时错误类别
固定探针无用户数据的受控输入输出结构和不变量符合预期候选标记不兼容
缓存对应源摘要、系统与缓存身份命中条件与当前候选一致安全重建而非误用旧缓存

活动指针只在全部门禁后原子替换

原子启用不等于把整个大目录瞬间复制到活动位置。更可靠的做法是候选目录一旦验证通过就变为不可变,活动状态只保存一个很小的版本指针。新指针先写入同一文件系统中的临时文件,刷新内容与父目录后,再用原子重命名替换活动指针。推理请求开始时读取一次指针并固定对应目录,整个请求生命周期不再跟随指针变化。

同一文件系统是关键边界,跨卷移动可能退化为复制和删除,不能假定具有原子语义。进程内还要协调并发:激活线程替换指针不应关闭已有模型实例,新的请求读取新版本,旧请求继续持有旧快照直到完成。多进程应用需要使用所有进程都能观察到的指针与版本租约,并在应用重启时重新读取指针和验证候选目录。

下面示例验证 manifest 中全部文件的相对路径、长度和 SHA-256,确认候选版本后,把 JSON 指针写到同目录临时文件并使用 os.replace 原子替换。它不验证发布签名,也没有实现平台模型编译,因此只能解释“完整候选到活动指针”的最后一步。真实应用还要在调用函数前完成可信清单验证、兼容测试和持久化刷新策略,并把 base_dir 限定在应用私有数据目录。

  • 候选目录验证完成后保持不可变
  • 指针临时文件和活动指针位于同一文件系统
  • 替换前刷新指针内容和必要目录元数据
  • 每个推理请求固定一次版本快照
  • 旧实例不会在处理中被强制关闭
  • 应用重启后从活动指针恢复而非猜测版本
验证候选文件后原子替换活动版本指针
from pathlib import Path
import hashlib
import json
import os
import sys

if len(sys.argv) != 3:
    raise SystemExit("usage: activate.py model_root manifest.json")

model_root = Path(sys.argv[1]).resolve()
manifest_path = Path(sys.argv[2]).resolve()
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
version = manifest.get("version")
files = manifest.get("files")

if not version or not isinstance(files, list) or not files:
    raise SystemExit("manifest has no versioned file set")

candidate = (model_root / "candidates" / version).resolve()
if candidate.parent != (model_root / "candidates").resolve():
    raise SystemExit("candidate path escaped the model root")

for entry in files:
    relative = Path(entry.get("path", ""))
    expected = entry.get("sha256", "")
    if relative.is_absolute() or ".." in relative.parts:
        raise SystemExit(f"unsafe manifest path: {relative}")
    target = (candidate / relative).resolve()
    if candidate not in target.parents or not target.is_file():
        raise SystemExit(f"candidate file missing: {relative}")
    if target.stat().st_size != entry.get("length"):
        raise SystemExit(f"candidate length mismatch: {relative}")
    actual = hashlib.sha256(target.read_bytes()).hexdigest()
    if actual != expected:
        raise SystemExit(f"candidate digest mismatch: {relative}")

pointer = model_root / "active.json"
staged = model_root / "active.json.next"
staged.write_text(json.dumps({"version": version}), encoding="utf-8")
with staged.open("rb") as handle:
    os.fsync(handle.fileno())
os.replace(staged, pointer)
print(f"activated verified model version {version}")

失败回退与坏版本抑制要提前设计

最安全的失败动作通常是继续使用上一已知可用版本,而不是立即删除候选或退回应用内置模型。上一版本必须在启用新版本后保留一段稳定观察期,且其文件、编译产物和必要关联资产保持完整。若新版本在真实请求中出现加载失败、输出不变量破坏或错误预算越界,回退只需把活动指针原子切回已验证版本,新请求使用旧版本,正在执行的请求按原快照完成。

坏版本需要抑制,否则服务端持续返回同一候选会造成下载、编译、失败和回退循环。客户端可记录候选摘要、失败阶段、错误类别、设备兼容条件和下一次允许重试时间,只有清单版本、候选摘要、运行环境或服务端策略发生可解释变化后才重试。记录不应包含模型内容或用户输入,服务端也不应把客户端单次失败自动扩大成所有设备不兼容。

回退本身也受版本策略约束。TUF 风格的回滚保护用于拒绝未授权旧元数据,不应阻止客户端在本地两个已授权候选之间执行安全回退。工程判断是分别维护“可接受发布版本集合”和“当前活动版本”,回退目标必须仍在授权集合、未过期策略允许范围内且具有本地完整证据。没有目标版本文件或证据时,不能只修改字符串指针。

失败阶段与回退决策
失败阶段旧版本状态候选处理下一次重试条件
清单验证保持活动拒绝且不下载可信元数据或信任策略变化
文件下载保持活动保留可验证分片或隔离网络恢复且候选摘要未变
摘要校验保持活动标记候选不可用清单或文件摘要变化
编译或试加载保持活动记录设备兼容失败运行环境或候选发生变化
切换指针旧指针继续有效重新读取当前活动状态本地存储状态恢复
真实请求观察原子切回旧版坏版本进入抑制列表新候选或明确策略解除

旧版本清理要晚于引用释放和证据归档

清理与启用如果在同一临界区执行,最容易把回退能力和在途请求一起删除。新版本切换后,旧模型实例可能仍被当前请求、后台任务或另一个进程持有。版本目录应有租约或引用记录,清理器只处理不再活动、没有有效租约、已过稳定观察期且不属于保留回退集合的候选。无法确认引用状态时宁可延迟清理,也不能凭目录修改时间推断无人使用。

缓存需要单独建账。Manage Core AI model caching 指出缓存可能因源模型、系统版本、存储压力或清理策略失效,因此缓存不应承担唯一模型副本或回退证据的职责。清理时先区分可信源文件、运行时编译产物、可重建缓存和下载残片;存储压力下优先移除可重建、非活动且无租约的内容,唯一已知可用版本应受到保护。

发布记录最终应绑定清单摘要、全部文件摘要、供应链声明引用、目标设备条件、编译或加载回执、活动指针变更、回退演练和清理边界。结论只能写到真实证据覆盖的范围,例如指定候选在列出设备与状态机路径上通过,不能宣称所有设备永久无故障。准备评估时,可通过御盾中央平台提交模型清单、关联文件、运行时矩阵和更新状态机,由双方先固定候选身份与验收范围。

  • 活动版本和上一已知可用版本不会被容量清理
  • 跨线程与跨进程引用都有可判断租约
  • 源模型、编译产物、缓存和残片分类清楚
  • 清理失败不会影响当前推理路径
  • 回退演练完成后再缩短旧版保留窗口
  • 发布与清理记录都绑定同一候选摘要

事实依据与适用边界

以下内容区分官方事实、本文工程判断和不能外推的范围,避免把设计建议写成未经验证的产品结论。

本文判断事实或工程依据适用限制
动态 Core ML 模型需要下载到本地、编译,并从编译产物初始化。Download and compile Core ML models 描述了下载、编译和加载动态 Core ML 模型的官方路径。该流程不自动定义发布者认证、版本授权、完整关联文件或回滚策略。
更新客户端需要处理签名、版本、过期和一致快照等元数据验证。The Update Framework specification 定义了面向回滚、冻结和元数据混搭风险的更新验证角色与流程。TUF 是通用更新框架,不直接决定移动模型的业务授权、灰度和性能门禁。
构建证明可以把产物主体与构建者、构建类型、外部参数和依赖材料关联。SLSA Provenance v1.1 描述 provenance 的主体和构建相关字段。provenance 只能说明记录的构建过程,不能单独证明目标设备运行安全或模型质量。
供应链声明可以用产物摘要绑定有类型的声明负载。in-toto Attestation Statement v1 定义 subject 与 predicate 的通用 statement 结构。声明格式不保证内容真实,仍需验证签名者、策略、声明类型和实际摘要。
模型缓存可能随源模型、系统版本、存储压力和清理策略失效或重建。Manage Core AI model caching 描述 Core AI 模型专用化与缓存管理的相关语义。Core AI 缓存语义不能直接外推到全部 Core ML、LiteRT 或其他移动运行时。
模型元数据可能包含输入输出说明、归一化参数、标签和关联文件。LiteRT model metadata 描述模型 metadata 与关联文件可承载的内容类型。元数据存在不等于内容可信,主模型摘要通过也不能覆盖未列出的关联资产。
活动切换应发生在全部文件验证和运行时准备之后,并只替换短小活动指针。工程判断:不可变候选目录加同文件系统原子指针替换,可以缩小半成品进入推理路径的窗口。具体文件系统、跨进程可见性、持久化和原子语义必须由目标平台项目测试确认。
失败回退、坏版本抑制和旧版本清理必须与新版本启用解耦。工程判断:分离状态可以保留上一已知可用版本,并避免同一坏候选持续触发更新循环。回退窗口、错误预算、租约和存储策略需要真实业务与设备证据,本文不提供通用数值。

工程常见问题

模型文件 SHA-256 正确后能否立即启用?

不能。摘要只证明文件与可信清单中的目标一致,还要验证清单授权、版本和过期状态、全部关联资产、平台兼容、运行时编译或加载及固定探针。

为什么不能直接把新模型覆盖到当前活动路径?

覆盖过程中进程终止、存储不足或并发读取会留下混合状态。应先在非活动目录完成候选,再原子替换短小指针,让在途请求继续持有旧快照。

模型主文件验证通过后,tokenizer 和标签可以继续用旧版吗?

不能默认混用。关联文件可能改变输入处理和输出解释,必须由同一可信清单声明并逐项校验。只有显式兼容策略和业务回归支持时才能复用。

SLSA provenance 或 in-toto 声明能否替代设备端回归?

不能。它们把构建过程或声明负载与产物身份绑定,但不证明特定设备能编译、加载并正确执行模型。设备兼容和业务探针仍需独立完成。

新版本激活后应立即删除旧版本吗?

不应立即删除。旧版本需要覆盖稳定观察和安全回退,在途请求或其他进程也可能仍持有引用。只有租约释放且清理条件满足后才回收。

申请移动端模型更新安全评估前需要准备什么?

准备可信清单、模型和关联文件、构建证明引用、运行时矩阵、更新状态机、失败回退方案及代表性候选,再通过御盾中央平台提交申请。

想用自己的 App 验证?

提交候选包、目标系统和关键业务路径,申请御盾 PoC 与兼容性评估。

继续阅读: 移动 AI 应用的运行时安全边界