先看结论与判断条件

  • 退场清单从主体和资源两个方向盘点,人员账号、服务身份、共享账号、令牌与第三方支持入口不能只查一个系统。
  • 每条访问决定必须是 revoke、transfer 或 retain-with-boundary 之一,并明确资源 owner、批准人、完成条件与回执。
  • 样本、mapping、符号、日志和工单是否保留属于资料治理;权限退场只决定谁在什么条件下继续访问。
  • 生产签名私钥不应成为供应商日常持有物,Android 与 Apple 的签名身份、账号和托管责任要分开回收。
  • 服务账号、CI 凭据、API 令牌和 webhook 要按实际依赖顺序轮换或撤销,避免先关账号导致无法识别残余调用。
  • 退场完成必须由独立复核确认登录失败、令牌失效、共享资源有新 owner,不能只凭工单状态或口头通知。

权限退场是访问关系收尾,不等于删除全部资料

项目结束常被简化为关闭供应商账号,但软件加固过程可能跨越文件传输、代码或制品仓库、处理平台、CI、对象存储、诊断日志、工单、即时沟通、签名服务和渠道后台。人员账号只是主体之一,服务身份、临时令牌、共享链接、受邀组织和支持通道也可能继续有效。退场要以主体到资源的访问关系为基本记录。

本任务只解决谁还能访问什么以及如何撤销、转移或受限保留,不决定样本和证据应该保存多久。mapping、符号、测试回执或构建证明可能仍需由客户指定角色保管,但供应商不必因此继续保持原权限。资料保留和销毁按独立政策处理,访问清单只记录保留责任人、读取边界和最终回执。

NIST SP 800-218 SSDF 把来源、构建、验证、变更与供应链风险纳入安全软件开发实践。退场可以把这些实践转成可执行问题:谁仍能读来源与产物,谁能改变构建或配置,谁持有验证证据,谁能继续触发发布。SSDF 不规定具体产品能力或统一撤权步骤,因此实际系统和组织角色仍需逐项盘点。

退场范围的主体与资源
对象示例主要风险退场动作
人员主体个人账号和访客离场后继续登录禁用并验证
服务主体CI bot 与集成无人识别残余调用迁移或撤销
临时能力令牌和共享链接过期策略失效轮换或作废
样本资源输入输出候选继续下载复制转移 owner
证据资源日志 mapping 符号诊断材料外泄最小化读取
高权限入口签名与渠道发布身份失控专门复核

先从身份提供方和应用侧同时导出访问清单

身份提供方可以列出用户、组、角色、登录时间和应用授权,但某些平台还维护本地账号、部署密钥、机器人、访问令牌和共享链接。资源系统又可能有直接 ACL,未必完全继承企业身份。因此盘点要双向进行:从所有供应商人员和服务身份查资源,也从每个加固资源查当前成员与凭据,最后对差异逐项解释。

主体记录包含公开安全的 subjectRef、类型、所属组织、业务联系人、认证方式、当前角色和离场日期,不保存密码、令牌内容或恢复码。资源记录包含 resourceRef、类别、owner、敏感级别和系统管理员。访问关系再记录 permission、来源、到期、使用目的、处理决定和回执。三层分开后,可以看出同一账号通过组、直接授权和令牌获得的多条路径。

共享账号与共同负责必须被拆开。共享账号无法可靠证明实际使用人,应在退场前迁移到具名主体或受控服务身份,并轮换认证材料。资源 owner 也必须唯一,研发团队或双方不能代替可执行负责人。尚未找到 owner 的仓库或日志空间标记 blocked,不能为赶进度直接删除或默认继续开放。

访问关系的最小字段
字段来源用途缺失处置
subjectRef身份系统识别访问主体停止关闭声明
resourceRef资源目录识别目标系统补充资源 owner
permissionACL 或角色判断能力范围按高权限处理
grantSource组直接或令牌发现多条路径逐条撤销
expiresAt授权记录识别临时访问不得假设已过期
receiptRef执行回执证明动作完成保持 blocked

人员账号与服务身份需要不同的撤权顺序

人员账号适合先冻结新增授权,再导出当前会话、组和资源成员关系,通知负责人后禁用登录、撤销会话与应用授权,最后验证无法重新认证。服务身份则可能支撑构建、加固任务、报告同步或通知,不能直接照搬人员离场流程。先识别调用方、权限和依赖,迁移到客户控制身份,再轮换凭据并观察旧身份不再产生调用。

临时令牌、部署密钥、SSH 公钥、API key、OAuth 应用授权、webhook secret 和机器证书都要作为独立访问关系。清单只记录公开标识、用途、scope、创建者、到期与撤销回执,不保存秘密值。若令牌由个人账号创建,禁用人员账号后仍可能有效,必须在资源侧确认其状态。

撤权顺序由风险与恢复能力共同决定。高权限人工登录可以优先禁用,自动化凭据需要在替代身份验证通过后切换;仍处于事件调查或紧急修复窗口的访问可以受限保留,但必须有到期、最小权限、审批和监控。临时保留不是无限延期,每次延长都产生新批准记录。

身份类型的退场动作
身份先决检查主要动作验证
个人账号组和直接授权禁用撤销会话登录失败
访客账号外部组织邀请移除访客关系成员列表消失
服务账号任务和依赖迁移后禁用旧调用归零
API 令牌scope 与调用者轮换撤销旧令牌失败
部署密钥仓库和流水线替换删除旧 key 无权
紧急访问hold 与批准降权限加到期到期自动关闭

样本、日志、mapping 与符号要转移 owner 后再撤权

加固输入、输出候选、崩溃日志、mapping、符号、测试回执和工单附件可能分散在上传区、对象存储、处理平台和支持系统。退场前为每类资源确认客户接收人、候选摘要、访问边界和状态。需要保留的材料先转移 owner 与受控副本,再撤销供应商读取;不再需要的材料进入批准的销毁流程,而不是由退场人员自行删除。

OWASP MASVS-PRIVACY-1 强调最小化敏感数据访问,并把第三方 SDK 的数据行为纳入责任边界。应用到离场时,应问供应商是否仍能读取含业务数据的样本或日志,第三方支持工具是否保留副本,以及诊断导出是否包含用户信息。该控制不替代业务数据分类、同意与合规审查,但支持按最小必要原则收紧访问。

mapping 和符号既可能支持崩溃诊断,也可能暴露代码结构,不能放在无人负责的公开共享空间。转移时记录材料摘要、适用候选、权限组、加密与审计位置;退场回执确认供应商成员和旧分享链接失效。保留期限由证据政策决定,本文不重复时长和销毁细则。

项目材料的 owner 转移
资源接收条件撤权条件验证回执
输入样本候选与来源清单客户副本已核对旧下载失败
输出候选输入输出关系发布方已接收供应商权限移除
诊断日志脱敏和案件索引调查状态明确成员列表与审计
mapping绑定版本与摘要诊断 owner 接收分享链接失效
符号文件候选与平台身份崩溃系统接收旧 token 作废
工单附件缺陷和结论索引后续联系人确定外部访客移除

构建证明和测试证据保留,不等于原访问继续有效

SLSA Provenance v1.1 把产物主体、构建者、构建类型、外部参数和材料绑定。项目结束后,客户可能需要保留 provenance 来解释候选来源,但可以把证明复制到客户控制的证据库并验证签名,不必继续开放原构建系统。访问退场记录应区分证据内容的保管责任与生成系统的操作权限。

in-toto Attestation Statement v1 用 subject 摘要和有类型的 predicate 组织声明。转移声明时核对 subject 是否指向实际候选、签名者是否仍可信、验证工具是否可用,并记录新 owner。声明格式不保证事实真实,转移成功也不意味着供应商原账号可以保留;相反,旧主体权限应在接收验证完成后按清单撤销。

测试报告、加固配置说明和缺陷回执采用同样原则:保留可审计副本,关闭生成或编辑权限。只读访问也需要明确用途和到期,不能因为不能修改就无限开放。若后续支持需要供应商重新查看证据,应从新工单发起临时最小权限,而不是复活整个项目角色。

签名、渠道和发布入口必须单独做高权限复核

Sign your Android app 区分应用签名密钥、上传密钥、证书和 Play App Signing 责任。退场清单要确认供应商是否拥有 Play 组织成员、上传能力、签名服务调用权、证书下载或发布流水线权限。生产私钥原则上不应成为普通项目材料;若存在特殊托管或调用关系,必须由密钥 owner 另行轮换、撤销和复核。

Apple app code signing process 说明 Apple 平台通过代码签名、证书和运行验证建立应用身份。Apple 团队成员、证书、profile、CI 签名身份和分发账号应按平台机制独立检查,不能沿用 Android 清单字段推测。移除人员成员不代表机器证书或 CI 凭据已失效,必须在签名服务和流水线两侧验证。

高权限复核采用双人确认:资源 owner 执行或见证撤销,发布负责人验证旧主体不能签名、上传或改变渠道配置。验证使用受控的无副作用动作,例如权限查询或被拒绝的测试认证,不发布真实版本。任何无法验证的入口保持 blocked,并冻结项目完成声明。

发布高权限退场清单
入口身份对象撤权动作验证边界
Android 组织人员与服务成员移除角色无法查看应用
上传能力上传身份撤销或轮换测试认证失败
签名服务调用角色移除策略无签名权限
Apple 团队成员角色移除成员团队资源不可见
CI 签名机器身份轮换证书凭据旧任务失败
渠道配置发布管理员转移 owner变更被拒绝

用顺序、回执与例外状态避免不可恢复的误关

退场顺序一般是冻结新增授权、完成访问盘点、指定资源 owner、转移必要资料、迁移自动化、撤销高风险人工权限、轮换机器凭据、关闭共享链接,最后执行独立验证。具体顺序按系统依赖调整,但每步要有前置条件。先删除唯一服务账号可能中断证据导出,先移除 owner 又可能让共享资源无人管理。

回执来自执行系统而不是关闭人自述。账号禁用保留身份平台状态,令牌撤销保留资源端结果,成员移除保留最新 ACL,owner 转移保留新主体接受,材料转移保留摘要核对。工单完成只是索引,必须链接真实系统回执。若系统不提供不可变回执,至少由第二人读取状态并签署复核。

例外使用 blocked 或 retained-with-boundary,写清资源、剩余权限、理由、批准人、补偿控制、到期与复核日期。事件调查、法务 hold 或紧急支持可能需要短期读取,但不能恢复写入、签名或发布权限。例外到期仍未关闭时自动回到阻塞,不因项目行政结束而从清单消失。

撤权顺序与证据
阶段前置条件动作回执
冻结项目结束决定禁止新增授权策略状态
盘点系统清单完整双向对账差异报告
转移新 owner 接受迁移资料与服务接收摘要
撤销依赖已迁移禁用账号令牌系统状态
验证动作全部提交独立认证检查失败回执
例外阻塞理由明确受限保留到期与批准

用代码发现离场后仍有效的账号和无人负责资源

下面的 Python 示例读取公开安全的访问退场清单,检查 subjectRef、resourceRef、permission、expiresAt、disposition、status、resourceOwner、retentionOwner 和 receiptRef。它会发现离场日期之后仍 active 的访问、已 revoked 却没有回执的记录、需要 retain 却没有保留责任人的资源,以及 owner 写成双方、共同负责或待定的模糊记录。

示例不读取真实身份系统、不执行撤销,也不包含令牌、密码、私钥、客户数据或内部地址。日期采用明确格式,离场后仍有效并非自动违法,而是必须进入 retained-with-boundary 并给出批准和到期。转移状态要求新 resourceOwner,撤销状态要求 receiptRef。实际系统还应使用唯一索引防止重复关系。

校验通过只说明清单字段闭合,不能证明账号真的无法登录。每条 completed 记录仍需由资源系统状态、访问失败测试或 ACL 快照验证。若代码发现阻塞,不要删除记录或改离场日期来取得通过,应补 owner、回执或受限保留决定。

校验软件加固项目访问退场清单
import json
import re
import sys
from datetime import date
from pathlib import Path

REQUIRED = {
    'accessId',
    'subjectRef',
    'resourceRef',
    'permission',
    'expiresAt',
    'disposition',
    'status',
    'resourceOwner',
    'retentionOwner',
    'receiptRef',
}
FORBIDDEN = {'token', 'password', 'privateKey', 'customerData'}
DISPOSITIONS = {'revoke', 'transfer', 'retain-with-boundary'}
STATUSES = {'planned', 'blocked', 'active', 'completed'}
AMBIGUOUS = re.compile(r'^(both|joint|tbd|双方|共同负责|待定)$', re.I)

def stop(message):
    raise SystemExit(message)

def parse_day(value, field, index):
    try:
        return date.fromisoformat(str(value))
    except ValueError:
        stop(f'record {index} has invalid {field}')

def required_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, offboarding_date):
    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:
        stop(f'record {index} lacks fields: {missing}')
    disposition = record['disposition']
    status = record['status']
    if disposition not in DISPOSITIONS or status not in STATUSES:
        stop(f'record {index} has invalid disposition or status')
    owner = required_text(record, 'resourceOwner', index)
    if AMBIGUOUS.fullmatch(owner):
        stop(f'record {index} has ambiguous resource owner')
    expires = parse_day(record['expiresAt'], 'expiresAt', index)
    if status == 'active' and expires >= offboarding_date:
        stop(f'record {index} remains active after offboarding')
    if disposition == 'revoke' and status == 'completed':
        if not str(record['receiptRef']).strip():
            stop(f'record {index} is revoked without receipt')
    if disposition == 'transfer' and status == 'completed':
        required_text(record, 'resourceOwner', index)
    if disposition == 'retain-with-boundary':
        retention_owner = str(record['retentionOwner']).strip()
        if not retention_owner or AMBIGUOUS.fullmatch(retention_owner):
            stop(f'record {index} has no retention owner')
    identity = (required_text(record, 'subjectRef', index), required_text(record, 'resourceRef', index), required_text(record, 'permission', index))
    return required_text(record, 'accessId', index), identity

if len(sys.argv) != 2:
    stop('usage: validate_offboarding.py offboarding.json')
source = Path(sys.argv[1])
if not source.is_file():
    raise SystemExit('offboarding file is missing')
data = json.loads(source.read_text(encoding='utf-8'))
if not isinstance(data, dict):
    raise SystemExit('offboarding root must be an object')
offboarding_date = parse_day(data.get('offboardingDate'), 'offboardingDate', 'root')
records = data.get('access')
if not isinstance(records, list) or not records:
    raise SystemExit('access records are missing')
results = [validate(record, index, offboarding_date) for index, record in enumerate(records)]
access_ids = [item[0] for item in results]
identities = [item[1] for item in results]
if len(access_ids) != len(set(access_ids)):
    raise SystemExit('accessId is not unique')
if len(identities) != len(set(identities)):
    raise SystemExit('subject resource permission relation is duplicated')
print(json.dumps({'accessRecords': len(records), 'status': 'pass'}))

最终复核只关闭有系统证据的访问关系

最终会议逐项打开主体、资源、权限、处理决定、owner、到期和回执。completed 表示系统动作与独立验证都已完成;blocked 表示缺少 owner、依赖未迁移或回执不可得;retained-with-boundary 表示经批准继续最小访问。unknown 不能默认视为已关闭,也不能因供应商联系人离职而从清单删除。

复核还要抽查反向路径:从关键样本仓库、日志、mapping、符号、工单、签名与渠道系统重新导出成员和令牌,确认没有清单外主体。随后验证旧身份无法登录或调用,新 owner 能接收必要资料,自动化在新身份下正常运行。任何验证失败都重新打开对应关系,不需要重跑无关系统。

准备退场时,可先整理主体、资源、权限、凭据类型、资料 owner、依赖、离场日期和例外,再参考本站的软件加固证据保留政策区分访问与保留。需要进一步评估项目收尾,可携带脱敏访问清单和回执索引通过御盾中央平台提交申请。实际撤权与签名状态必须以目标系统回执为准。

事实依据与适用边界

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

本文判断事实或工程依据适用限制
安全项目应保留来源、构建、验证和变更证据,并处理供应链风险。NIST SP 800-218 SSDF 提供组织级安全软件开发实践与供应链风险方向。SSDF 不定义具体加固产品功能,也不规定所有组织统一的账号退场步骤。
移动端应最小化敏感数据访问,并把第三方数据行为纳入责任边界。OWASP MASVS-PRIVACY-1 明确要求最小化敏感数据访问并审视第三方 SDK。该控制不能替代业务数据分类、同意记录、合同和合规审查。
构建证明可以把产物主体与构建者、构建类型、外部参数和材料关联。SLSA Provenance v1.1 定义 provenance 模型及主要字段语义。provenance 可转移保管但不单独证明运行时安全,也不要求原构建访问继续存在。
供应链声明应把产物摘要与有类型的声明负载绑定。in-toto Attestation Statement v1 定义 subject、predicateType 与 predicate 的结构。声明格式不保证内容真实,仍需可信签名者、验证门禁与候选摘要复算。
Android 发布链需要区分应用签名密钥、上传密钥、证书和 Play App Signing 责任。Sign your Android app 说明 Android 应用签名与托管签名职责。官方文档不能确认某个实际账号或候选使用正确身份,仍需目标系统回执。
Apple 平台通过代码签名、证书与运行验证建立 App 身份。Apple app code signing process 说明 Apple 平台应用签名与验证过程。Apple 签名链与 Android 签名机制不同,退场清单必须分别核对。
权限退场应从人员、服务身份、临时能力和资源 ACL 两个方向交叉盘点。工程判断:单一身份系统无法覆盖本地账号、令牌、共享链接和资源直接授权。具体系统清单取决于真实架构,文章不声称任何项目已经完成盘点。
资料保留与原访问持续有效是两个不同决定。工程判断:证据可以转移到客户控制资源,同时撤销供应商账号和编辑能力。保留期限、销毁、hold 和法律责任由独立政策及授权角色决定。

工程常见问题

停用供应商主账号后,软件加固项目权限就全部收回了吗?

不能这样判断。还要检查服务账号、组成员、直接 ACL、临时令牌、部署密钥、共享链接、工单访客、签名服务和渠道成员,并逐项验证失效。

项目结束后 mapping 和符号需要删除吗?

本文不决定保留期限。先指定客户保管 owner、绑定候选并转移受控副本,再撤销供应商访问;是否销毁由证据保留和 hold 政策决定。

服务账号为什么不能和人员账号一起立即禁用?

服务身份可能支撑构建、同步或通知。应先识别依赖并迁移到客户控制身份,再轮换凭据、观察旧调用停止,避免误关导致证据或业务中断。

只读访问是否可以在项目结束后一直保留?

不应默认保留。只读仍可复制样本和诊断信息,需要明确用途、最小范围、批准人、到期和审计;后续支持可以按新工单临时授权。

如何证明令牌和账号真的已经撤销?

使用目标系统状态、ACL 快照、令牌撤销结果和受控认证失败测试,并由独立复核人确认。工单完成或口头通知只能作为索引。

向御盾中央平台提交项目退场评估前准备什么?

准备主体、资源、权限、授权来源、临时凭据标识、资料 owner、自动化依赖、签名渠道入口、离场日期、处理决定、例外、批准人与系统回执索引。

想用自己的 App 验证?

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

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