先看结论与判断条件
- 交接轴是 applicationId、签名身份和发布候选,不是人员姓名或项目群。
- 配置、证据、例外、签名、渠道和支持责任必须分别指定唯一接收人。
- 生产私钥不进入普通交接包,只转移受控调用权、角色与审计责任。
- 继任者要在无副作用条件下验证读取、批准和回滚能力。
- 旧负责人撤权覆盖人员账号、组、令牌、会话和直接 ACL。
- 结案回执要证明新 owner 可履责且旧 owner 已失去不再需要的权限。
用应用身份而不是人员名称建立交接轴
先把applicationId、Bundle ID、签名身份与候选写成可复核对象,而不是凭页面印象判断。应用标识、证书摘要、候选 SHA-256、渠道和当前版本应与最终候选摘要、版本、环境和操作时间绑定,记录值来自系统回执、测试日志或受控导出。只有这些输入同源,交接是否始终指向同一个商业 App 和发布链才有解释力;文件名相似、人员记忆或聊天截图都不能代替对象身份。
从发布渠道反查应用与证书,不从项目名称猜测。执行前冻结会改变结果的开关、账号属性、时间窗和网络条件,执行中只改变一个变量,执行后保存输入、观察值与判定。每个通过项都要有正向路径和拒绝或失败路径;只记录“成功”会漏掉错误默认值、未授权访问、取消恢复和边界状态。
应用或证书身份只靠口头描述时先标记 blocked,并把剩余未知写进报告。内部负责人变更不等同于更换加固供应商。这项限制不能靠扩大权限、修改生产导出面或补写未经验证的数字绕过。若后续证据改变,只重开对应记录,不重复执行已经有稳定回执的无关步骤。
| 检查对象 | 输入证据 | 判定动作 | 失败处置 |
|---|---|---|---|
| applicationId、Bundle ID、签名身份与候选身份 | 应用标识、证书摘要、候选 SHA-256、渠道和当前版本 | 交接是否始终指向同一个商业 App 和发布链 | 应用或证书身份只靠口头描述则阻塞 |
| 同源基线 | 候选摘要与时间窗 | 锁定比较对象 | 来源不明则阻塞 |
| 执行记录 | 从发布渠道反查应用与证书,不从项目名称猜测 | 保存系统回执 | 只有口头结论则补证 |
| 适用边界 | 内部负责人变更不等同于更换加固供应商 | 限制结论范围 | 不得扩大为全局结论 |
锁定当前候选与加固配置基线
保护配置、工具版本、输入输出和回滚基线要从状态变化而不是最终页面开始核对。把配置摘要、候选关系、工具版本、变更记录和回滚入口按发生顺序保留,并为每一步写明触发者、前置条件、候选版本和回执来源。这样可以判断继任者接收的配置是否能解释当前线上候选发生在读取、计算、传输、系统调度还是业务处理阶段,避免把多个故障压成一句“加固后不可用”。
导出只读配置和候选旁车清单,由双方复算摘要。清单至少包含对象标识、预期、观察、证据引用、责任人和复核状态。自动校验适合发现缺字段、重复记录与不一致结果,最终业务含义仍由熟悉应用的人判断。若依赖第三方平台,还要区分客户端实际状态、平台控制面显示和异步回执的时间差。
验收不能只看操作完成或命令退出码。配置包无法绑定当前发布候选说明链路仍缺少可归因证据;配置可解释候选但不能替代运行时兼容验收。结论应分成已确认事实、工程判断和适用限制,尤其不能把一次测试通过扩大成所有版本、设备、渠道或客户场景都通过。
| 检查对象 | 输入证据 | 判定动作 | 失败处置 |
|---|---|---|---|
| 保护配置、工具版本、输入输出和回滚基线身份 | 配置摘要、候选关系、工具版本、变更记录和回滚入口 | 继任者接收的配置是否能解释当前线上候选 | 配置包无法绑定当前发布候选则阻塞 |
| 同源基线 | 候选摘要与时间窗 | 锁定比较对象 | 来源不明则阻塞 |
| 执行记录 | 导出只读配置和候选旁车清单,由双方复算摘要 | 保存系统回执 | 只有口头结论则补证 |
| 适用边界 | 配置可解释候选但不能替代运行时兼容验收 | 限制结论范围 | 不得扩大为全局结论 |
移交业务批准和风险例外
处理业务 owner、批准矩阵、风险例外和到期时,先固定应用身份、候选摘要、平台版本和可变条件。批准范围、例外理由、补偿控制、到期和复核时间必须来自同一轮执行,不能把不同设备或不同发布时间的结果拼在一起。证据链闭合后再讨论哪些决策权已经转移、哪些仍需业务批准,否则重试成功也只能说明环境变了,不能支持技术结论。
逐条例外重新确认接受者,不自动继承过期批准。基线与最终候选使用相同设备、数据准备、账号权限和触发脚本,差异只保留必要变量。遇到失败先保存现场,再按最小范围补测;重装、清数据、切账号或换网络都会重置条件,必须作为新用例登记,不能覆盖原记录。
出现风险例外没有新接受者或到期,应回到上一份同源输入,比较首次分叉点并保留两侧回执。业务接受风险不能免除技术证据与复核责任。临时绕行可以用于恢复业务,但必须记录批准、到期和回退条件,不能让临时措施替代根因验证或变成长期未审计配置。
| 检查对象 | 输入证据 | 判定动作 | 失败处置 |
|---|---|---|---|
| 业务 owner、批准矩阵、风险例外和到期身份 | 批准范围、例外理由、补偿控制、到期和复核时间 | 哪些决策权已经转移、哪些仍需业务批准 | 风险例外没有新接受者或到期则阻塞 |
| 同源基线 | 候选摘要与时间窗 | 锁定比较对象 | 来源不明则阻塞 |
| 执行记录 | 逐条例外重新确认接受者,不自动继承过期批准 | 保存系统回执 | 只有口头结论则补证 |
| 适用边界 | 业务接受风险不能免除技术证据与复核责任 | 限制结论范围 | 不得扩大为全局结论 |
分别处理 Android 与 Apple 签名控制
先把Android 密钥角色与 Apple 签名身份写成可复核对象,而不是凭页面印象判断。密钥 owner、调用角色、证书、上传身份和审计回执应与最终候选摘要、版本、环境和操作时间绑定,记录值来自系统回执、测试日志或受控导出。只有这些输入同源,签名操作权是否转移且私钥责任没有模糊才有解释力;文件名相似、人员记忆或聊天截图都不能代替对象身份。
按平台机制转移角色并使用无副作用权限查询验证。执行前冻结会改变结果的开关、账号属性、时间窗和网络条件,执行中只改变一个变量,执行后保存输入、观察值与判定。每个通过项都要有正向路径和拒绝或失败路径;只记录“成功”会漏掉错误默认值、未授权访问、取消恢复和边界状态。
签名入口仍由共同负责或待定角色持有时先标记 blocked,并把剩余未知写进报告。Android 与 Apple 签名机制不同,必须分别核对。这项限制不能靠扩大权限、修改生产导出面或补写未经验证的数字绕过。若后续证据改变,只重开对应记录,不重复执行已经有稳定回执的无关步骤。
转移渠道、流水线和发布角色
商店组织、CI 服务身份和发布审批要从状态变化而不是最终页面开始核对。把成员角色、流水线 owner、发布门禁、轨道和回滚权限按发生顺序保留,并为每一步写明触发者、前置条件、候选版本和回执来源。这样可以判断继任者能否执行受控发布与回滚决策发生在读取、计算、传输、系统调度还是业务处理阶段,避免把多个故障压成一句“加固后不可用”。
在客户控制身份下验证查看、审批和回滚入口。清单至少包含对象标识、预期、观察、证据引用、责任人和复核状态。自动校验适合发现缺字段、重复记录与不一致结果,最终业务含义仍由熟悉应用的人判断。若依赖第三方平台,还要区分客户端实际状态、平台控制面显示和异步回执的时间差。
验收不能只看操作完成或命令退出码。渠道角色已转但 CI 机器身份无人负责说明链路仍缺少可归因证据;角色可转移不代表真实发布或回滚已经执行。结论应分成已确认事实、工程判断和适用限制,尤其不能把一次测试通过扩大成所有版本、设备、渠道或客户场景都通过。
| 检查对象 | 输入证据 | 判定动作 | 失败处置 |
|---|---|---|---|
| 商店组织、CI 服务身份和发布审批身份 | 成员角色、流水线 owner、发布门禁、轨道和回滚权限 | 继任者能否执行受控发布与回滚决策 | 渠道角色已转但 CI 机器身份无人负责则阻塞 |
| 同源基线 | 候选摘要与时间窗 | 锁定比较对象 | 来源不明则阻塞 |
| 执行记录 | 在客户控制身份下验证查看、审批和回滚入口 | 保存系统回执 | 只有口头结论则补证 |
| 适用边界 | 角色可转移不代表真实发布或回滚已经执行 | 限制结论范围 | 不得扩大为全局结论 |
移交证据索引与诊断材料 owner
处理mapping、符号、测试回执、证明与工单时,先固定应用身份、候选摘要、平台版本和可变条件。材料摘要、适用候选、存储位置、读取边界和保留 owner必须来自同一轮执行,不能把不同设备或不同发布时间的结果拼在一起。证据链闭合后再讨论诊断与审计材料是否有明确保管责任,否则重试成功也只能说明环境变了,不能支持技术结论。
转移受控副本和索引,不复制不必要的敏感日志。基线与最终候选使用相同设备、数据准备、账号权限和触发脚本,差异只保留必要变量。遇到失败先保存现场,再按最小范围补测;重装、清数据、切账号或换网络都会重置条件,必须作为新用例登记,不能覆盖原记录。
出现材料可访问却没有保留与诊断 owner,应回到上一份同源输入,比较首次分叉点并保留两侧回执。资料保留时长由独立政策决定,交接只定义控制权。临时绕行可以用于恢复业务,但必须记录批准、到期和回退条件,不能让临时措施替代根因验证或变成长期未审计配置。
先验证继任能力再撤销旧访问
先把旧负责人账号、组、令牌、会话和 ACL写成可复核对象,而不是凭页面印象判断。登录状态、组成员、直接授权、令牌状态和撤销回执应与最终候选摘要、版本、环境和操作时间绑定,记录值来自系统回执、测试日志或受控导出。只有这些输入同源,旧主体是否已失去所有不再需要的访问路径才有解释力;文件名相似、人员记忆或聊天截图都不能代替对象身份。
先冻结新增授权,再撤销会话、令牌、组与直接 ACL。执行前冻结会改变结果的开关、账号属性、时间窗和网络条件,执行中只改变一个变量,执行后保存输入、观察值与判定。每个通过项都要有正向路径和拒绝或失败路径;只记录“成功”会漏掉错误默认值、未授权访问、取消恢复和边界状态。
旧账号禁用但令牌或直接 ACL 仍有效时先标记 blocked,并把剩余未知写进报告。撤权动作以目标系统回执为准,不执行无授权删除。这项限制不能靠扩大权限、修改生产导出面或补写未经验证的数字绕过。若后续证据改变,只重开对应记录,不重复执行已经有稳定回执的无关步骤。
| 检查对象 | 输入证据 | 判定动作 | 失败处置 |
|---|---|---|---|
| 旧负责人账号、组、令牌、会话和 ACL身份 | 登录状态、组成员、直接授权、令牌状态和撤销回执 | 旧主体是否已失去所有不再需要的访问路径 | 旧账号禁用但令牌或直接 ACL 仍有效则阻塞 |
| 同源基线 | 候选摘要与时间窗 | 锁定比较对象 | 来源不明则阻塞 |
| 执行记录 | 先冻结新增授权,再撤销会话、令牌、组与直接 ACL | 保存系统回执 | 只有口头结论则补证 |
| 适用边界 | 撤权动作以目标系统回执为准,不执行无授权删除 | 限制结论范围 | 不得扩大为全局结论 |
用代码检查交接关系是否闭环
脱敏交接清单中的控制关系要从状态变化而不是最终页面开始核对。把handoffId、应用、现任、继任、预期 owner、观察 owner 和证据按发生顺序保留,并为每一步写明触发者、前置条件、候选版本和回执来源。这样可以判断每项控制是否有唯一新 owner 与撤权证据发生在读取、计算、传输、系统调度还是业务处理阶段,避免把多个故障压成一句“加固后不可用”。
把控制项写入 JSON,让校验器拒绝 owner 不一致和空回执。清单至少包含对象标识、预期、观察、证据引用、责任人和复核状态。自动校验适合发现缺字段、重复记录与不一致结果,最终业务含义仍由熟悉应用的人判断。若依赖第三方平台,还要区分客户端实际状态、平台控制面显示和异步回执的时间差。
验收不能只看操作完成或命令退出码。预期继任 owner 与系统观察 owner 不一致说明链路仍缺少可归因证据;代码只检查公开安全字段,不读取私钥、令牌或客户数据。结论应分成已确认事实、工程判断和适用限制,尤其不能把一次测试通过扩大成所有版本、设备、渠道或客户场景都通过。
import hashlib
import json
import sys
from pathlib import Path
REQUIRED = set([
'handoffId',
'applicationId',
'currentOwner',
'successorOwner',
'expectedOwner',
'observedOwner',
'result',
'evidenceRef'
])
ALLOWED_RESULTS = set([
'pass',
'blocked',
'mismatch'
])
FORBIDDEN = {'token', 'password', 'privateKey', 'secret', 'customerData'}
def stop(message):
raise SystemExit(message)
def digest(path):
hasher = hashlib.sha256()
with path.open('rb') as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b''):
hasher.update(chunk)
return hasher.hexdigest()
def text(record, field, index):
value = str(record[field]).strip()
if not value:
stop(f'record {index} has empty {field}')
return value
def validate(record, index):
if not isinstance(record, dict):
stop(f'record {index} is not an object')
if FORBIDDEN.intersection(record):
stop(f'record {index} contains forbidden fields')
missing = sorted(REQUIRED - record.keys())
if missing:
raise SystemExit(f'record {index} lacks fields: {missing}')
for field in REQUIRED:
if field != 'applicationId':
text(record, field, index)
result = text(record, 'result', index)
if result not in ALLOWED_RESULTS:
stop(f'record {index} has an unsupported result')
expected = text(record, 'expectedOwner', index)
observed = text(record, 'observedOwner', index)
evidence = text(record, 'evidenceRef', index)
if expected != observed and result == 'pass':
stop(f'record {index} passes despite a mismatch')
if expected != observed and not evidence:
stop(f'record {index} has a mismatch without evidence')
return text(record, 'handoffId', index)
if len(sys.argv) != 2:
stop('usage: python validate_handoff.py public-checks.json')
source = Path(sys.argv[1]).expanduser().resolve()
if not source.is_file():
stop('public check file is missing')
raw = source.read_bytes()
data = json.loads(raw.decode('utf-8'))
records = data.get('handoffs') if isinstance(data, dict) else None
if not isinstance(records, list) or not records:
raise SystemExit('handoffs must be a non-empty list')
identifiers = [validate(record, index) for index, record in enumerate(records)]
if len(identifiers) != len(set(identifiers)):
raise SystemExit('record identifiers are duplicated')
summary = {
'inputSha256': hashlib.sha256(raw).hexdigest(),
'records': len(records),
'results': {state: sum(1 for item in records if item['result'] == state) for state in sorted(ALLOWED_RESULTS)},
'status': 'pass',
}
print(json.dumps(summary, ensure_ascii=False, sort_keys=True))由双人复核关闭控制权交接
处理继任 owner、旧 owner、资源管理员与审计人时,先固定应用身份、候选摘要、平台版本和可变条件。接收验证、撤权结果、例外、批准和最终签署必须来自同一轮执行,不能把不同设备或不同发布时间的结果拼在一起。证据链闭合后再讨论交接可以关闭、受限保留还是继续阻塞,否则重试成功也只能说明环境变了,不能支持技术结论。
资源管理员执行、继任者验证、独立审计人抽查。基线与最终候选使用相同设备、数据准备、账号权限和触发脚本,差异只保留必要变量。遇到失败先保存现场,再按最小范围补测;重装、清数据、切账号或换网络都会重置条件,必须作为新用例登记,不能覆盖原记录。
出现新 owner 未验证能力却宣布交接完成,应回到上一份同源输入,比较首次分叉点并保留两侧回执。结案仅覆盖清单中的应用、资源和验证范围。临时绕行可以用于恢复业务,但必须记录批准、到期和回退条件,不能让临时措施替代根因验证或变成长期未审计配置。
事实依据与适用边界
以下内容区分官方事实、本文工程判断和不能外推的范围,避免把设计建议写成未经验证的产品结论。
| 本文判断 | 事实或工程依据 | 适用限制 |
|---|---|---|
| Android 使用应用签名建立更新身份,并由不同签名方案覆盖不同文件区域和平台版本。 | AOSP app signing:Android 使用应用签名建立更新身份,并由不同签名方案覆盖不同文件区域和平台版本。 | 签名有效只证明完整性与签名者身份,不证明业务代码安全。 |
| 发布需要区分应用签名密钥、上传密钥、证书与 Play App Signing 责任。 | Sign your Android app:发布需要区分应用签名密钥、上传密钥、证书与 Play App Signing 责任。 | 文档不能确认某个实际包使用了正确生产证书。 |
| Apple 平台通过代码签名、证书与运行验证建立 App 身份。 | Apple app code signing process:Apple 平台通过代码签名、证书与运行验证建立 App 身份。 | Apple 签名链与 Android APK 签名方案不能混为一套证据。 |
| 构建证明应绑定产物主体、构建者、构建类型、外部参数与依赖材料。 | SLSA Provenance v1.1:构建证明应绑定产物主体、构建者、构建类型、外部参数与依赖材料。 | provenance 只能证明记录的构建过程,不能单独证明运行时安全性。 |
| 供应链证明应把产物摘要与有类型的声明负载绑定,避免报告与候选包错配。 | in-toto Attestation Statement v1:供应链证明应把产物摘要与有类型的声明负载绑定,避免报告与候选包错配。 | 声明格式不保证声明内容真实,仍需可信签名者和门禁核验。 |
| 更新客户端应验证签名、版本、过期时间和一致快照,以识别回滚、冻结和混搭风险。 | The Update Framework specification:更新客户端应验证签名、版本、过期时间和一致快照,以识别回滚、冻结和混搭风险。 | TUF 是更新框架,不直接规定移动端模型或 APK 的业务授权策略。 |
| 控制权交接必须同时证明继任者可履责和旧负责人已撤权。 | 工程判断:只完成一侧会留下无人负责资源或残余访问,无法形成闭环。 | 实际撤权范围取决于身份系统和资源 ACL,文章不声称任何账号已撤销。 |
| 签名责任应按平台角色与受控调用权移交,而不是复制生产私钥。 | 工程判断:角色、托管密钥和审计机制可以保持密钥边界并实现责任转移。 | 特殊密钥托管安排需要密钥 owner 和平台规则另行批准。 |
工程常见问题
负责人变更是否需要重新加固全部 App?
不一定。先验证应用身份、当前候选、配置和控制权连续性;只有工具链、配置或候选发生实质变化时才决定复验范围。
生产签名私钥怎样交给新负责人?
普通交接不复制私钥,应转移平台角色、受控签名调用权、审批和审计责任,并按 Android、Apple 机制分别验证。
旧负责人账号禁用就算撤权吗?
不算。还要检查组、直接 ACL、会话、API 令牌、机器身份、渠道成员和共享链接。
风险例外可以自动继承吗?
不应。继任 owner 要重新确认范围、理由、补偿控制、到期和复核计划,过期或无人接受的例外保持阻塞。
怎样证明继任者已经接管?
让其在无副作用条件下验证配置读取、证据访问、审批和回滚入口,并由资源系统回执与第二人复核确认。
提交御盾交接评估要准备什么?
准备应用与证书标识、候选摘要、配置索引、签名渠道角色、CI 身份、证据 owner、风险例外、旧访问清单和系统回执。