先看结论与判断条件
- 公告标题不是匹配条件,必须抽取组件、版本范围、平台和利用前提。
- 企业应用台账要能追到最终候选、构建任务、依赖锁定结果与加固工具版本。
- 结果只分明确命中、明确不命中和证据不足,未知不能自动降级成不受影响。
- 冻结、复验和重发按暴露面与业务重要性排序,不因公告严重度一刀切。
- 供应商声称已缓解要绑定具体版本、配置与候选回执,不能只保存邮件。
- 结案必须保留公告版本、判断输入、批准人、验证回执和仍未覆盖的应用。
把安全公告翻译成机器可匹配条件
先把公告中的组件、版本范围与利用前提写成可复核对象,而不是凭页面印象判断。公告编号、产品、组件、版本区间、平台、前提与发布时间应与最终候选摘要、版本、环境和操作时间绑定,记录值来自系统回执、测试日志或受控导出。只有这些输入同源,公告是否适用于企业实际使用的工具或依赖才有解释力;文件名相似、人员记忆或聊天截图都不能代替对象身份。
保存公告原文与版本,逐条抽取范围并由第二人复核。执行前冻结会改变结果的开关、账号属性、时间窗和网络条件,执行中只改变一个变量,执行后保存输入、观察值与判定。每个通过项都要有正向路径和拒绝或失败路径;只记录“成功”会漏掉错误默认值、未授权访问、取消恢复和边界状态。
公告范围只剩自然语言且版本边界未确认时先标记 blocked,并把剩余未知写进报告。公告严重度和评分不能代替企业环境中的可达性判断。这项限制不能靠扩大权限、修改生产导出面或补写未经验证的数字绕过。若后续证据改变,只重开对应记录,不重复执行已经有稳定回执的无关步骤。
| 检查对象 | 输入证据 | 判定动作 | 失败处置 |
|---|---|---|---|
| 公告中的组件、版本范围与利用前提身份 | 公告编号、产品、组件、版本区间、平台、前提与发布时间 | 公告是否适用于企业实际使用的工具或依赖 | 公告范围只剩自然语言且版本边界未确认则阻塞 |
| 同源基线 | 候选摘要与时间窗 | 锁定比较对象 | 来源不明则阻塞 |
| 执行记录 | 保存公告原文与版本,逐条抽取范围并由第二人复核 | 保存系统回执 | 只有口头结论则补证 |
| 适用边界 | 公告严重度和评分不能代替企业环境中的可达性判断 | 限制结论范围 | 不得扩大为全局结论 |
从应用台账追到真实构建材料
应用、发布候选和构建任务关系要从状态变化而不是最终页面开始核对。把applicationId、候选摘要、构建标识、工具版本和材料索引按发生顺序保留,并为每一步写明触发者、前置条件、候选版本和回执来源。这样可以判断哪个线上 App 候选包含被公告点名的材料发生在读取、计算、传输、系统调度还是业务处理阶段,避免把多个故障压成一句“加固后不可用”。
从发布候选反查流水线和材料,不用当前开发分支代替历史构建。清单至少包含对象标识、预期、观察、证据引用、责任人和复核状态。自动校验适合发现缺字段、重复记录与不一致结果,最终业务含义仍由熟悉应用的人判断。若依赖第三方平台,还要区分客户端实际状态、平台控制面显示和异步回执的时间差。
验收不能只看操作完成或命令退出码。应用台账无法找到线上候选对应的构建材料说明链路仍缺少可归因证据;没有同一候选材料时只能标记未知,不能反推已受影响或已安全。结论应分成已确认事实、工程判断和适用限制,尤其不能把一次测试通过扩大成所有版本、设备、渠道或客户场景都通过。
| 检查对象 | 输入证据 | 判定动作 | 失败处置 |
|---|---|---|---|
| 应用、发布候选和构建任务关系身份 | applicationId、候选摘要、构建标识、工具版本和材料索引 | 哪个线上 App 候选包含被公告点名的材料 | 应用台账无法找到线上候选对应的构建材料则阻塞 |
| 同源基线 | 候选摘要与时间窗 | 锁定比较对象 | 来源不明则阻塞 |
| 执行记录 | 从发布候选反查流水线和材料,不用当前开发分支代替历史构建 | 保存系统回执 | 只有口头结论则补证 |
| 适用边界 | 没有同一候选材料时只能标记未知,不能反推已受影响或已安全 | 限制结论范围 | 不得扩大为全局结论 |
依赖解析结果要覆盖传递依赖和插件
处理直接依赖、传递依赖、插件与工具版本时,先固定应用身份、候选摘要、平台版本和可变条件。解析坐标、选择版本、来源仓库、依赖路径与锁定状态必须来自同一轮执行,不能把不同设备或不同发布时间的结果拼在一起。证据链闭合后再讨论传递链是否把受影响组件带入最终构建,否则重试成功也只能说明环境变了,不能支持技术结论。
导出解析报告和锁文件,沿依赖路径确认实际选择版本。基线与最终候选使用相同设备、数据准备、账号权限和触发脚本,差异只保留必要变量。遇到失败先保存现场,再按最小范围补测;重装、清数据、切账号或换网络都会重置条件,必须作为新用例登记,不能覆盖原记录。
出现依赖报告来自另一个变体或后续重跑,应回到上一份同源输入,比较首次分叉点并保留两侧回执。解析结果说明构建选择,不证明运行时路径一定可达。临时绕行可以用于恢复业务,但必须记录批准、到期和回退条件,不能让临时措施替代根因验证或变成长期未审计配置。
| 检查对象 | 输入证据 | 判定动作 | 失败处置 |
|---|---|---|---|
| 直接依赖、传递依赖、插件与工具版本身份 | 解析坐标、选择版本、来源仓库、依赖路径与锁定状态 | 传递链是否把受影响组件带入最终构建 | 依赖报告来自另一个变体或后续重跑则阻塞 |
| 同源基线 | 候选摘要与时间窗 | 锁定比较对象 | 来源不明则阻塞 |
| 执行记录 | 导出解析报告和锁文件,沿依赖路径确认实际选择版本 | 保存系统回执 | 只有口头结论则补证 |
| 适用边界 | 解析结果说明构建选择,不证明运行时路径一定可达 | 限制结论范围 | 不得扩大为全局结论 |
用三态分类避免把未知写成安全
先把命中、不命中和证据不足三种判断写成可复核对象,而不是凭页面印象判断。匹配字段、支持证据、反证、未知字段和复核人应与最终候选摘要、版本、环境和操作时间绑定,记录值来自系统回执、测试日志或受控导出。只有这些输入同源,记录应归入 affected、not-affected 还是 unknown才有解释力;文件名相似、人员记忆或聊天截图都不能代替对象身份。
为每个结论填写 claim、basis 与 boundary,未知字段明确列出。执行前冻结会改变结果的开关、账号属性、时间窗和网络条件,执行中只改变一个变量,执行后保存输入、观察值与判定。每个通过项都要有正向路径和拒绝或失败路径;只记录“成功”会漏掉错误默认值、未授权访问、取消恢复和边界状态。
缺材料记录却被标记为明确不受影响时先标记 blocked,并把剩余未知写进报告。不命中需要字段级反证,未知需要明确补证任务。这项限制不能靠扩大权限、修改生产导出面或补写未经验证的数字绕过。若后续证据改变,只重开对应记录,不重复执行已经有稳定回执的无关步骤。
冻结与复验按可达性和业务影响排序
受影响候选的冻结、复测和重发顺序要从状态变化而不是最终页面开始核对。把可达入口、部署范围、业务等级、临时控制和验证窗口按发生顺序保留,并为每一步写明触发者、前置条件、候选版本和回执来源。这样可以判断哪些应用先冻结发布、先复验或需要立即迁移发生在读取、计算、传输、系统调度还是业务处理阶段,避免把多个故障压成一句“加固后不可用”。
建立按风险排序的工作队列,只重测受影响边界和必要回归。清单至少包含对象标识、预期、观察、证据引用、责任人和复核状态。自动校验适合发现缺字段、重复记录与不一致结果,最终业务含义仍由熟悉应用的人判断。若依赖第三方平台,还要区分客户端实际状态、平台控制面显示和异步回执的时间差。
验收不能只看操作完成或命令退出码。高风险应用继续生成新候选且没有批准说明链路仍缺少可归因证据;应急顺序不承诺固定时长,并受发布窗口和业务恢复能力限制。结论应分成已确认事实、工程判断和适用限制,尤其不能把一次测试通过扩大成所有版本、设备、渠道或客户场景都通过。
| 检查对象 | 输入证据 | 判定动作 | 失败处置 |
|---|---|---|---|
| 受影响候选的冻结、复测和重发顺序身份 | 可达入口、部署范围、业务等级、临时控制和验证窗口 | 哪些应用先冻结发布、先复验或需要立即迁移 | 高风险应用继续生成新候选且没有批准则阻塞 |
| 同源基线 | 候选摘要与时间窗 | 锁定比较对象 | 来源不明则阻塞 |
| 执行记录 | 建立按风险排序的工作队列,只重测受影响边界和必要回归 | 保存系统回执 | 只有口头结论则补证 |
| 适用边界 | 应急顺序不承诺固定时长,并受发布窗口和业务恢复能力限制 | 限制结论范围 | 不得扩大为全局结论 |
验证缓解版本而不是接受口头承诺
处理供应商补丁、配置缓解与新工具版本时,先固定应用身份、候选摘要、平台版本和可变条件。修复版本、变更说明、迁移配置、候选摘要和复验结果必须来自同一轮执行,不能把不同设备或不同发布时间的结果拼在一起。证据链闭合后再讨论缓解是否真正进入指定候选并满足公告条件,否则重试成功也只能说明环境变了,不能支持技术结论。
在隔离环境用旧链基线与新链候选做差分并保存回滚点。基线与最终候选使用相同设备、数据准备、账号权限和触发脚本,差异只保留必要变量。遇到失败先保存现场,再按最小范围补测;重装、清数据、切账号或换网络都会重置条件,必须作为新用例登记,不能覆盖原记录。
出现补丁版本存在但实际候选仍使用旧组件,应回到上一份同源输入,比较首次分叉点并保留两侧回执。工具升级不自动证明兼容、安全或性能通过。临时绕行可以用于恢复业务,但必须记录批准、到期和回退条件,不能让临时措施替代根因验证或变成长期未审计配置。
候选证明和声明承担可追溯责任
先把provenance、attestation 与更新元数据写成可复核对象,而不是凭页面印象判断。subject 摘要、builder、材料、predicate 类型和签名验证应与最终候选摘要、版本、环境和操作时间绑定,记录值来自系统回执、测试日志或受控导出。只有这些输入同源,证明是否足以把判断绑定到不可替换的产物才有解释力;文件名相似、人员记忆或聊天截图都不能代替对象身份。
复算产物摘要并验证声明签名,再登记新的证据 owner。执行前冻结会改变结果的开关、账号属性、时间窗和网络条件,执行中只改变一个变量,执行后保存输入、观察值与判定。每个通过项都要有正向路径和拒绝或失败路径;只记录“成功”会漏掉错误默认值、未授权访问、取消恢复和边界状态。
声明主体摘要与发布候选不一致时先标记 blocked,并把剩余未知写进报告。证明格式不保证声明内容真实,仍需可信签名者与验证门禁。这项限制不能靠扩大权限、修改生产导出面或补写未经验证的数字绕过。若后续证据改变,只重开对应记录,不重复执行已经有稳定回执的无关步骤。
| 检查对象 | 输入证据 | 判定动作 | 失败处置 |
|---|---|---|---|
| provenance、attestation 与更新元数据身份 | subject 摘要、builder、材料、predicate 类型和签名验证 | 证明是否足以把判断绑定到不可替换的产物 | 声明主体摘要与发布候选不一致则阻塞 |
| 同源基线 | 候选摘要与时间窗 | 锁定比较对象 | 来源不明则阻塞 |
| 执行记录 | 复算产物摘要并验证声明签名,再登记新的证据 owner | 保存系统回执 | 只有口头结论则补证 |
| 适用边界 | 证明格式不保证声明内容真实,仍需可信签名者与验证门禁 | 限制结论范围 | 不得扩大为全局结论 |
用代码匹配公告条件与应用材料
脱敏公告条件和应用材料清单要从状态变化而不是最终页面开始核对。把规则标识、应用标识、预期分类、观察分类、状态和证据引用按发生顺序保留,并为每一步写明触发者、前置条件、候选版本和回执来源。这样可以判断每条匹配记录是否字段闭合且分类一致发生在读取、计算、传输、系统调度还是业务处理阶段,避免把多个故障压成一句“加固后不可用”。
把公开安全字段写成 JSON,由校验器拒绝重复和错误分类。清单至少包含对象标识、预期、观察、证据引用、责任人和复核状态。自动校验适合发现缺字段、重复记录与不一致结果,最终业务含义仍由熟悉应用的人判断。若依赖第三方平台,还要区分客户端实际状态、平台控制面显示和异步回执的时间差。
验收不能只看操作完成或命令退出码。预期分类和观察分类不一致却标记通过说明链路仍缺少可归因证据;代码只核对脱敏清单,不连接生产仓库或执行升级。结论应分成已确认事实、工程判断和适用限制,尤其不能把一次测试通过扩大成所有版本、设备、渠道或客户场景都通过。
import hashlib
import json
import sys
from pathlib import Path
REQUIRED = set([
'matchId',
'advisoryId',
'applicationId',
'candidateSha256',
'componentVersion',
'expectedClassification',
'observedClassification',
'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 != 'candidateSha256':
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, 'expectedClassification', index)
observed = text(record, 'observedClassification', 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, 'matchId', index)
if len(sys.argv) != 2:
stop('usage: python match_advisory.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('matches') if isinstance(data, dict) else None
if not isinstance(records, list) or not records:
raise SystemExit('matches 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 与安全负责人逐项签署,不以会议纪要代替回执。基线与最终候选使用相同设备、数据准备、账号权限和触发脚本,差异只保留必要变量。遇到失败先保存现场,再按最小范围补测;重装、清数据、切账号或换网络都会重置条件,必须作为新用例登记,不能覆盖原记录。
出现结案清单遗漏未知应用或没有验证回执,应回到上一份同源输入,比较首次分叉点并保留两侧回执。结案仅覆盖列明候选、公告版本和验证范围。临时绕行可以用于恢复业务,但必须记录批准、到期和回退条件,不能让临时措施替代根因验证或变成长期未审计配置。
事实依据与适用边界
以下内容区分官方事实、本文工程判断和不能外推的范围,避免把设计建议写成未经验证的产品结论。
| 本文判断 | 事实或工程依据 | 适用限制 |
|---|---|---|
| 安全发布应保留来源、构建、验证和变更证据,并把供应链风险纳入开发流程。 | NIST SP 800-218 SSDF:安全发布应保留来源、构建、验证和变更证据,并把供应链风险纳入开发流程。 | SSDF 是组织级实践框架,不定义某个 App 加固产品的具体功能。 |
| 更新客户端应验证签名、版本、过期时间和一致快照,以识别回滚、冻结和混搭风险。 | The Update Framework specification:更新客户端应验证签名、版本、过期时间和一致快照,以识别回滚、冻结和混搭风险。 | TUF 是更新框架,不直接规定移动端模型或 APK 的业务授权策略。 |
| 构建证明应绑定产物主体、构建者、构建类型、外部参数与依赖材料。 | SLSA Provenance v1.1:构建证明应绑定产物主体、构建者、构建类型、外部参数与依赖材料。 | provenance 只能证明记录的构建过程,不能单独证明运行时安全性。 |
| 供应链证明应把产物摘要与有类型的声明负载绑定,避免报告与候选包错配。 | in-toto Attestation Statement v1:供应链证明应把产物摘要与有类型的声明负载绑定,避免报告与候选包错配。 | 声明格式不保证声明内容真实,仍需可信签名者和门禁核验。 |
| 直接和传递依赖会形成版本解析图,解析结果变化可能导致运行时不兼容。 | Android Gradle dependency resolution:直接和传递依赖会形成版本解析图,解析结果变化可能导致运行时不兼容。 | 依赖树只能缩小排查范围,不能证明某个 SDK 是唯一根因。 |
| SDK 评审可参考版本采用、权限、可靠性、安全和数据处理指引。 | Google Play SDK Index:SDK 评审可参考版本采用、权限、可靠性、安全和数据处理指引。 | SDK Index 不覆盖所有私有或开源 SDK,也不替代二进制清单。 |
| 证据不足必须保留为 unknown,而不是推定不受影响。 | 工程判断:缺少候选材料时既不能证明版本命中,也不能形成字段级排除证据。 | unknown 的优先级仍需结合业务风险和可达性评估。 |
| 复验范围应由公告条件与真实构建材料的交集决定。 | 工程判断:按组件、版本、平台和利用前提映射,可以缩小测试范围并保留可解释依据。 | 公告未披露完整技术细节时,应采用保守范围并等待权威更新。 |
工程常见问题
公告点名的组件出现在仓库里,是否所有 App 都受影响?
不能直接判断。要核对最终候选实际选择的版本、变体、传递路径和利用前提,未进入候选的开发依赖不能当成命中。
没有历史 SBOM 或依赖报告怎么办?
标记 unknown,尝试从制品、流水线回执、锁文件和证明材料补证;不能用当前分支重跑结果代替历史候选。
供应商说新版本已修复,可以直接结案吗?
不能。要绑定修复说明、工具版本、配置、候选摘要和复验回执,并确认公告的缓解条件确实满足。
CVSS 很高是否必须立即重发全部 App?
严重度是排序输入,还要结合实际组件版本、可达性、发布范围和业务恢复风险。证据未知的高价值应用应优先补证。
明确不命中需要哪些证据?
至少有同一候选的构建材料或依赖解析结果,逐字段证明组件、版本、平台或利用前提不匹配,并保留复核人。
向御盾提交影响评估前准备什么?
准备公告原文、应用与候选清单、工具版本、依赖解析、构建证明、变体、签名渠道、当前分类和缺失证据。