先看结论与判断条件

  • 先明确停止上新、停止支持、停止服务和彻底退役的日期与范围;同一个产品在不同渠道可能处于不同状态。
  • 应用下架通常只改变后续分发,不能假定已安装副本自动消失,因此服务端、更新提示和用户迁移仍需单独设计。
  • 最后可支持候选必须用文件摘要、版本、签名身份、渠道和加固配置识别,不能只保存一个最终版文件名。
  • Android 与 Apple 的签名身份、证书和更新链分别留证;停止发布不等于可以立即销毁签名控制或撤销全部恢复能力。
  • 构建来源、provenance、attestation、加固配置、mapping、符号和测试回执要有接收人和访问边界,但具体保留期限属于独立政策。
  • 关闭流水线、账号、存储和供应商访问之前,先验证用户迁移、紧急修复、数据导出、事件调查和合规冻结没有继续依赖。

先定义退役状态,避免一句下架掩盖四种决定

停止上新表示不再向新用户分发,已安装用户仍可能继续运行;停止更新表示不再发布新客户端,但服务端可能继续;停止支持表示不再承诺常规修复;彻底退役才涉及关闭服务、终止更新路径、回收权限和按政策处置材料。四种状态的日期和责任不能混写。

同一 App 可能同时存在 Google Play、其他 Android 渠道、企业分发、Apple App Store 和历史侧载包,各渠道的可见性与用户覆盖不同。收尾清单逐渠道记录当前状态、最后版本、操作人、回执和仍可获取的用户范围,不能用主商店已下架推断全部分发结束。

工程判断是把退役作为有依赖的变更,而不是行政关闭工单。产品、研发、安全、发布、客服、法务和供应商分别批准自己控制的对象;最终 Accountable 角色确认所有未决依赖有去向。文章提供技术清单,不给出法律结论或御盾的固定服务承诺。

四种退役状态的技术含义
状态仍可能发生必须保留的能力不能直接推断
停止上新旧用户继续运行服务与支持判断安装副本消失
停止更新现有版本继续访问身份和事件处置无需安全响应
停止支持用户仍持有客户端通知与风险决定可以立即删证据
停止服务离线功能继续数据导出与迁移所有渠道已关闭
彻底退役历史争议或调查授权的证据与责任人材料无任何价值
恢复发布紧急修复或过渡版签名、构建和门禁旧包可直接重用

建立渠道和已安装版本的真实清单

渠道清单至少包含应用标识、商店或分发系统、国家或组织范围、当前状态、最后 versionCode 或构建号、签名身份、下架操作回执和负责人。企业内测、MDM、下载页与合作方镜像也要纳入,否则公开商店关闭后仍可能出现新的安装。 对仍可下载的历史安装器、合作伙伴缓存和企业设备策略,要写明发现方式、通知对象和关闭验证;无法控制的副本保留为风险缺口,不能用主渠道回执覆盖。

已安装版本无法只靠商店后台得出完整数字。团队可以结合服务端版本分布、发布后台和支持记录形成有边界的估计,并明确数据缺口。没有真实数据时写未知,不编造活跃量、下架完成率或剩余用户数;每项迁移动作标明适用版本。

下架不应破坏用户的必要数据出口。若 App 承担账号、凭据、离线内容或设备控制,产品负责人先确定替代入口、导出格式、通知方式和最后服务条件。客户端加固不能替代服务端迁移,已安装包也不应被当作可远程强制消失的对象。

逐渠道收尾记录的最小字段
字段来源核对动作缺失处置
应用标识构建与商店精确匹配停止合并记录
渠道状态渠道回执读取真实结果标记未知
最后版本发布记录绑定候选摘要追查历史包
签名身份签名回执核对证书或团队不得猜测
用户范围服务与发布数据说明口径保留数据缺口
负责人组织授权确认仍在职可响应重新指定角色

冻结最后可支持候选,而不是保存一个最终版文件名

最后可支持候选是退役后调查、用户恢复或过渡发布的锚点。记录文件 SHA-256、applicationId 或 Bundle ID、版本、平台、渠道、签名身份、来源构建、加固配置和测试回执。文件名、网盘时间或聊天中的最终版不能建立唯一身份。

若不同渠道对包做资源处理、重新签名或派生安装包,每个最终产物分别登记,并指向共同来源。provenance 可以把主体、构建者、构建类型、参数与材料关联;in-toto Statement 可用 subject 摘要绑定有类型的声明,但格式正确仍需要可信签名者和门禁核验。

冻结候选不等于宣称它永远安全或兼容。记录明确最后验证的设备、系统、业务范围、已知限制和未通过项。未来因平台变化或安全公告需要恢复发布时,应从保存的来源重新建立新候选和新门禁,不把历史通过直接继承。 恢复实验还要证明依赖仓库、构建镜像、许可证与工具入口在授权范围内可用;若只能读取二进制而无法重建,应把这种限制写进最终支持边界。

最后候选身份包的组成
对象唯一标识责任人边界
二进制文件摘要制品负责人不代表安全强度
构建来源提交与 provenance研发负责人记录不保证可重现
加固配置配置摘要与工具版本安全负责人不公开敏感规则
签名身份证书或团队标识发布负责人不保存私钥内容
测试证据候选与范围测试负责人仅适用已测条件
已知限制例外和失败事实风险负责人不得改写为通过

签名和更新能力要在退役窗口内保持可控

Android 应用签名建立更新身份,不同签名方案覆盖的文件区域和平台支持有所不同。退役清单要分清应用签名密钥、上传身份、证书和渠道角色,确认谁仍能为合法过渡版本执行签名。签名有效只说明身份和完整性,不说明业务安全。

Apple 平台通过代码签名、证书和运行验证建立 App 身份,证书、团队和分发方式与 Android 证据不能合并成一个签名已保存字段。分别记录最后候选、签名责任、撤销或到期影响和必要的恢复授权,不在普通清单中保存私钥。

签名控制何时关闭取决于是否仍有安全修复、用户迁移、法定通知或服务端切换需要。停止日常发布后可收紧权限、改为双人批准或离线保管,但立即销毁可能切断更新能力。具体处置由密钥政策批准,文章不规定通用期限。

签名与更新控制的退役决策
控制对象退役前确认关闭条件禁止做法
Android 应用身份最后版本与更新关系无剩余更新责任只记包名
上传身份渠道角色与替补渠道操作完成与应用密钥混写
Apple 签名身份团队、证书和分发迁移责任结束套用 Android 字段
签名服务账号权限和审计紧急窗口关闭保留长期共享账号
证书回执与候选绑定按政策归档只留截图
紧急更新能力触发和批准角色书面终止决定无人负责地保留

更新、回滚和冻结风险需要书面终态

The Update Framework specification 讨论更新元数据中的签名、版本、过期与一致快照,用于识别回滚、冻结和混搭风险。移动 App 不一定直接采用 TUF,但退役设计可以借用其问题意识:谁授权最后版本、旧版本是否还能被重新分发、元数据何时过期、恢复发布如何防止错包。

如果服务端仍允许旧客户端连接,要明确最低版本、功能限制、认证和下线响应。若计划发布过渡版,指定来源候选、签名、渠道、用户提示和回滚条件;若决定永不再更新,则由有权承担风险的负责人批准,并说明已安装用户的支持边界。

不能用远程开关代替所有退役控制。开关可能无法到达离线设备,客户端默认值也可能在服务关闭后继续工作。测试至少覆盖在线、离线、服务端拒绝、过渡版本和旧版本恢复,记录可观察结果,不宣称一个开关能撤销全部安装副本。

退役后的版本决策表
情形客户端动作服务端动作批准证据
继续有限支持显示明确状态保留必要接口范围与终止条件
发布过渡版迁移和提示支持新旧切换候选与门禁
停止更新维持最后功能边界拒绝新增能力风险接受
强制升级不可行提供替代入口保护数据出口用户迁移计划
发现高危问题限制受影响功能触发事件流程紧急批准
彻底终止离线状态可解释关闭并留审计最终收尾签字

构建、加固和诊断材料要完成责任移交

退役包至少能回答最后候选从哪里来、由什么工具和配置处理、谁批准、如何签名、覆盖哪些测试。NIST SSDF 支持保留来源、构建、验证与变更证据并处理供应链风险;它是组织级实践,不定义某个加固产品的功能或保留期限。

源码、依赖锁定、构建脚本、provenance、attestation、加固配置摘要、mapping、Native 符号和测试回执的访问级别不同。清单为每类材料指定保管人、恢复方法和销毁冻结条件;具体保存多久、如何校验与销毁由已有证据保留政策负责,本文不提供第二套期限。

供应商交付物完成接收后,客户要验证自己能够打开索引、按摘要找到材料并识别限制。只收到网盘链接、邮件附件或报告截图不算控制权转移。若某材料由供应商专有系统托管,收尾记录写明可导出范围、终止后的可访问性和替代证据。 接收方应在供应商访问关闭前完成一次按摘要检索和恢复演练,记录缺失索引、损坏文件和受限格式;演练失败时,材料状态保持 blocked,不能用已下载文件数量代替可恢复性。

收尾材料的责任分组
材料收尾动作继续用途不得包含
来源构建固定提交与依赖重建和调查未授权源码副本
provenance绑定主体与材料追溯来源虚构构建事实
加固配置保存摘要与边界解释最后候选公开敏感规则
mapping 与符号绑定候选诊断历史问题无归属文件
测试回执保存范围和失败支持边界判断改写原始结论
例外记录保留责任与终态解释风险决定无限期默认继承

先完成用户与服务端迁移,再回收项目访问

客户端退役通常伴随 API、账号、推送、配置、存储和客服入口变化。逐项确认替代服务、数据导出、账号关闭、设备解绑、离线数据解释和投诉入口。服务端拒绝旧客户端时返回可识别状态,不让用户陷入无限重试或无说明空白页。 对每个接口还要定义最后允许的客户端范围、拒绝响应、数据出口、客服说明和监控终止时间;依赖未关闭前,相关服务账号和审计日志不能先行撤销。

访问回收覆盖开发者账号、签名服务、CI/CD、制品库、供应商门户、样本存储、诊断平台和工单系统。每个主体记录权限、最后用途、撤销时间、执行人和复核人;共享账号无法证明谁完成了最后操作,应先拆分身份再关闭。

删除动作必须等待依赖解除。若存在安全事件、合同争议、法务冻结、用户数据请求、紧急更新或尚未验收的迁移,清单把对象标为 hold 并指定解除批准人。hold 不代表永久保存,解除后仍按已批准政策执行处置并保存回执。

用代码检查退役清单中的未闭合依赖

下面的 Python 脚本读取公开安全的退役清单 JSON,不连接商店、不删除文件、不撤销账号。它要求渠道、最后候选、签名控制、更新路径、用户迁移、证据移交、访问回收和最终批准八类工作都有负责人、状态和证据引用。

脚本把完成状态与依赖分开检查。一个步骤标记 completed 时,所有 dependsOn 必须已经完成;要删除材料时,activeHolds 必须为空且 deletionApprovedBy 不能缺失。这样可以阻止先关签名账号、后发现还要发过渡版的顺序错误。

结构检查只证明清单字段闭合,不证明渠道实际下架、用户已经迁移或材料已经销毁。真实结论仍需打开 evidenceRef 核对回执。示例拒绝私钥、token 和用户数据字段,避免把收尾台账变成敏感材料副本。

校验 App 退役与加固交付收尾清单
import json
import sys
from pathlib import Path

REQUIRED_STEPS = {
    'channels',
    'final_candidate',
    'signing_control',
    'update_path',
    'user_migration',
    'evidence_handoff',
    'access_offboarding',
    'final_approval',
}
SENSITIVE_FIELDS = {'privateKey', 'token', 'userData', 'password'}
ALLOWED_STATUS = {'planned', 'blocked', 'hold', 'completed'}

if len(sys.argv) != 2:
    raise SystemExit('usage: validate_decommission.py closeout.json')
input_path = Path(sys.argv[1])
if not input_path.is_file():
    raise SystemExit('closeout file is missing')
data = json.loads(input_path.read_text(encoding='utf-8'))
steps = data.get('steps')
if not isinstance(steps, list) or not steps:
    raise SystemExit('steps array is missing')
records = {}
for index, step in enumerate(steps):
    if not isinstance(step, dict):
        raise SystemExit(f'step {index} is not an object')
    leaked = SENSITIVE_FIELDS.intersection(step)
    if leaked:
        raise SystemExit(f'step {index} contains sensitive fields: {sorted(leaked)}')
    required = {'id', 'owner', 'status', 'evidenceRef', 'dependsOn'}
    if not required.issubset(step):
        raise SystemExit(f'step {index} is missing required fields')
    step_id = str(step['id']).strip()
    if not step_id or step_id in records:
        raise SystemExit(f'step {index} has an invalid or duplicate id')
    if step['status'] not in ALLOWED_STATUS:
        raise SystemExit(f'step {step_id} has an invalid status')
    if not str(step['owner']).strip():
        raise SystemExit(f'step {step_id} has no owner')
    if not isinstance(step['dependsOn'], list):
        raise SystemExit(f'step {step_id} has invalid dependencies')
    records[step_id] = step
missing = sorted(REQUIRED_STEPS - records.keys())
if missing:
    raise SystemExit(f'closeout lacks required steps: {missing}')
for step_id, step in records.items():
    unknown = sorted(set(step['dependsOn']) - records.keys())
    if unknown:
        raise SystemExit(f'step {step_id} has unknown dependencies: {unknown}')
    if step['status'] == 'completed':
        incomplete = [item for item in step['dependsOn'] if records[item]['status'] != 'completed']
        if incomplete:
            raise SystemExit(f'step {step_id} completed before dependencies: {incomplete}')
    if step['status'] == 'completed' and not str(step['evidenceRef']).strip():
        raise SystemExit(f'step {step_id} completed without evidence')
delete_request = data.get('deleteRequest')
if delete_request is not None:
    if not isinstance(delete_request, dict):
        raise SystemExit('deleteRequest is not an object')
    if delete_request.get('activeHolds'):
        raise SystemExit('deletion is blocked by active holds')
    if not str(delete_request.get('deletionApprovedBy', '')).strip():
        raise SystemExit('deletion has no approver')
print(json.dumps({'steps': len(records), 'requiredComplete': True, 'status': 'pass'}))

最终批准只关闭已核实的工作,不抹掉未知项

最终评审逐项打开渠道回执、最后候选、签名控制、更新决定、用户迁移、证据移交和撤权记录。完成表示动作与回执都已核实;blocked 表示有明确障碍和责任人;unknown 不能被默认通过。无法形成证据的事项保留为缺口。

收尾报告写清仍可恢复的能力、已经不可逆关闭的能力、剩余风险和未来联系人。若日后需要安全修复或监管响应,团队可以从最后候选和责任索引恢复判断,而不是重新猜测签名、配置与渠道历史。退役完成也不代表历史版本从所有设备消失。

需要制定材料保留和销毁细则时,可继续阅读本站的软件加固证据保留政策,再携带应用清单、渠道、最后候选和迁移计划申请御盾项目收尾评估。申请、登录与控制台动作由御盾中央平台承接;本文不虚构御盾已完成任何客户退役项目。

事实依据与适用边界

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

本文判断事实或工程依据适用限制
更新系统需要关注签名、版本、过期时间与一致快照,以识别回滚、冻结和混搭风险。The Update Framework specification 定义更新元数据和客户端验证模型。TUF 不直接规定移动 App 的业务授权、商店下架或御盾项目收尾流程。
安全发布应保留来源、构建、验证、变更和供应链风险证据。NIST SP 800-218 SSDF 提供组织级安全软件开发实践。SSDF 不定义某个加固产品功能,也不规定所有组织统一的保留期限。
构建证明可以把产物主体与构建者、构建类型、参数和材料关联。SLSA Provenance v1.1 定义 provenance 的模型和字段语义。provenance 记录不能单独证明运行安全、渠道下架或用户迁移已经完成。
供应链声明应使用摘要标识产物,并给声明负载明确类型。in-toto Attestation Statement v1 定义 subject、predicateType 与 predicate。格式正确不保证声明真实,仍需可信签名者、策略和门禁核验。
Android 应用签名建立应用身份、完整性和更新关系。AOSP app signing 说明 Android 应用签名和 APK 签名方案。签名有效不证明业务安全、加固有效或退役动作完成。
Apple 平台通过代码签名、证书和运行验证建立 App 身份。Apple app code signing process 说明 Apple 平台代码签名过程。Apple 签名链不能直接替代 Android 签名证据,也不证明商店下架完成。
退役收尾应区分停止分发、停止更新、停止支持、停止服务与彻底退役。工程判断:这些状态影响已安装用户、签名能力、服务端和证据责任的范围不同。具体日期、通知和法务义务由业务与适用规则决定,文章不提供法律结论。
销毁材料前必须确认更新、迁移、事件、争议和冻结依赖均有终态。工程判断:过早删除可能切断紧急修复、用户恢复或调查所需证据。这不是无限保存建议;解除 hold 后仍按批准的保留与销毁政策执行。

工程常见问题

App 从应用商店下架后,已安装版本会自动失效吗?

不能这样假定。下架通常影响后续分发,已安装副本、离线功能和服务端访问要分别设计迁移、限制与通知。

退役后可以立即销毁生产签名密钥吗?

不应默认立即销毁。先确认没有过渡版、安全修复、用户迁移或事件响应责任,再由密钥政策规定的负责人批准收紧、保管或销毁。

只保存最后 APK 或 IPA 文件是否足够?

不够。还要绑定文件摘要、版本、签名身份、来源构建、加固配置、渠道、测试范围和已知限制,否则无法证明文件身份。

退役收尾与加固证据保留政策有什么区别?

收尾清单决定哪些渠道、能力、责任和依赖已经关闭;证据保留政策负责材料的访问、校验、期限、hold 与销毁细则。

服务端已经关闭,还需要保留旧客户端的支持边界吗?

需要说明已安装版本会看到什么、数据如何导出、离线功能如何表现、投诉入口在哪里,以及是否存在过渡或恢复路径。

申请御盾协助做 App 退役收尾需要准备什么?

准备应用与渠道清单、最后候选摘要、签名控制、更新决定、用户迁移、证据索引、访问清单和 hold 状态,再通过御盾中央平台提交。

想用自己的 App 验证?

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

继续阅读: 御盾 VMP 接入前准备清单