安全门禁上线后的第一个月,阻断记录通常很干净:每次失败都有清晰的规则编号、扫描路径和修复建议。到了第三个月,流程里开始出现另一种模式——合并请求描述里写着“已和安全团队口头确认过”,CI 配置里多了一行路径排除,审批依据散落在即时消息和工单系统之间。门禁没有坏,但它正在被例外慢慢绕过,直到某天有人发现,阻断策略对大多数活跃分支已经不起作用了。

先分清检查和门禁
在讨论例外之前,需要把两个常被混用的概念分开。安全检查负责识别问题,安全门禁负责对检查结果应用策略并决定动作——阻断合并、允许合并但记录、或标记为需要人工判断。静态分析结果常以 SARIF 承载,扫描器输出结构化告警,门禁消费这些结果并执行阈值判断和分流。例外管理属于门禁层面的策略治理,不是关掉扫描器,也不是修改规则本身。
这个区分之所以重要,是因为例外应当依附于门禁的决策记录,而不是依附于扫描器的配置。扫描器可以一直报同一个问题,门禁可以选择放行,但放行这件事必须有期限、有对象、有可追溯的依据。如果把例外直接写成扫描器侧的规则禁用,门禁就失去了对例外的可见性,复核和到期也无从谈起。
例外的粒度决定了它的影响范围
例外粒度越粗,管理成本越低,但影响范围越大。一条“全局忽略 CWE-89”的例外和一条“仅对 scripts/legacy/ 目录下特定文件的某条规则放行到下一个季度末”的例外,风险完全不同。
常见的粒度大致有四种。
规则级
适用于整条规则在特定仓库或分支范围内被确认不适用的情况。比如某条规则只针对 Java 的某个框架,而当前仓库全部是 Python。规则级例外影响面最大,应当限定在仓库或分支范围,并给出明确的技术理由,而不是只写“误报”两个字。
文件或路径级
适用于遗留代码、生成代码或 vendored 第三方代码。路径级例外需要精确到目录或文件 glob,避免使用宽泛的 **/* 模式。下面是一个在 CI 配置中限定路径范围的示意:
# 结构示意,字段名与可用性需按所用 CI 平台版本文档核验
security_gate:
exceptions:
- rule: "sast.java.command-injection"
scope:
paths:
- "legacy/importers/**"
expires: "2026-09-30"
owner: "platform-security@example.com"
reason: "遗留导入器使用白名单参数化,规则命中为模型限制导致的误报"不同平台对例外字段的支持程度不同,有些平台只能通过合并请求标签或外部工单记录例外,实际字段需要按所用平台版本文档核验。
依赖坐标级
适用于 SCA 告警。用包名和版本范围限定,而不是把整个依赖扫描级别调低。一个常见错误是把某条 CVE 的例外写成“忽略所有 high 级 SCA 告警”,这样下一次真正需要阻断的依赖问题也会被一起放过。
密钥暴露类型级
适用于密钥扫描。这类例外通常和误报有关——测试 fixture 中的假密钥、文档中的示例值,或已经轮换但历史提交中仍可检索到的凭据。例外应当绑定到具体的检测器类型和路径模式,并附带确认记录,说明凭据已失效或从未有效。
粒度选择的原则可以归结为一句话:例外的影响范围应当刚好覆盖被接受的风险,不多不少。如果一条例外同时覆盖五个互不相关的模块,说明粒度太粗,复核时很难判断它是否仍然成立。
把审批、期限、责任人与证据绑定到合并请求和配置
例外不是一次性的对话,而是一条有生命周期的记录。最小可用的例外记录至少应包含以下字段:
- 决策结果和理由:为什么放行,技术依据是什么
- 受影响的范围:规则、路径、依赖坐标或密钥类型
- 审批人:谁批准了这条例外,审批人应有对应的权限
- 到期时间:明确到日期,不用“下季度”或“待定”
- 未解决的修复动作:如果这不是误报,修复计划是什么
- 责任人:谁负责修复或推动复核
这些字段应尽量跟代码放在一起,而不是只存在于外部工单系统。合并请求本身是天然的审计线索,例外配置作为代码变更进入版本控制后,谁在什么时候、基于什么理由修改了门禁行为,可以直接从提交历史和评审记录里回看。
实际操作中,可以在合并请求模板里增加一段例外声明,要求填写上述字段。CI 校验脚本可以检查描述是否包含对应字段,但不必过度自动化到逐字段正则匹配的程度——那会催生新的形式主义。更可行的做法是让安全评审人对照模板逐项确认,并在合并请求上留下批准评论。合并请求的批准评论本身就是一份可追溯的证据,比散落在聊天工具里的“已同意”更容易在复核时找回。
到期提醒需要附着在责任人和配置上
到期提醒失败最常见的原因是通知只发到了一个没人看的邮件列表。更有效的做法是把到期时间同时记录在两个地方:例外配置本身作为代码字段存在,同时有一个轻量的定时任务或 CI 定时流水线读取该字段。
到期提醒的提前量建议不少于一到两周,给复核和修复留出处理时间。提醒内容不需要复杂,但应当包含:例外标识、原始理由摘要、责任人、剩余天数,以及复核入口。如果平台支持在过期后自动将门禁从放行切换到阻断,应当在过期后保留一段宽限期再切换,避免在发布窗口内突然阻断流水线。宽限期本身也应有上限,否则又会变成一个新的无期限例外。
复核时检查什么
到期复核不是走一遍审批形式,而是回答三个问题:
- 原始理由是否仍然成立?误报是否已被规则更新消除?遗留代码是否已经迁移或删除?
- 修复动作是否有进展?如果没有进展,是资源问题还是优先级问题?
- 受影响的对象是否仍然在门禁覆盖范围内?依赖坐标是否已经升级?路径是否仍然存在?
如果三个问题中任何一个的答案发生了变化,例外就不应原样续期。复核通过时更新到期时间并记录复核人;复核不通过时进入下一节的恢复阻断流程。

复核失败后恢复阻断的步骤
复核失败或例外到期后的恢复阻断,需要在不影响正常发布的前提下完成。可以按以下顺序操作:
- 确认例外配置已经过期或已被标记为拒绝,门禁行为切换到阻断。
- 在测试分支上运行一次完整流水线,确认阻断确实生效,而不是被其他例外或降级策略覆盖。
- 如果阻断影响了正在进行的发布,评估是否可以在本次变更中完成修复后再合并,而不是再次放行。恢复阻断的目的是让修复发生,不是制造新的紧急例外。
- 记录恢复阻断的时间和操作人,作为例外生命周期的收尾证据。
如果组织有多级门禁——提交时服务端检查、CI 阶段、注册表准入、部署准入——需要在每个执行点上确认例外状态一致,避免出现流水线放行但准入拒绝,或相反的情况。多级门禁的例外状态不一致,往往比单点例外更难排查,因为阻断看起来生效了,实际只在某一层生效。
回滚例外配置
例外配置本身也是代码,也会出现需要回滚的情况:范围写错了、审批给错了对象、到期时间覆盖了一批本不该放行的规则。回滚方式取决于配置的存放形式。
- 如果例外写在仓库内的配置文件中,回滚就是对配置文件的反向提交,并通过合并请求重新评审。
- 如果例外通过 CI 平台变量或集中式策略管理,应保留变更前的配置快照,回滚前先在非生产分支验证门禁行为。
- 如果例外依赖外部工单系统,回滚需要同时关闭工单和对应的门禁标记,防止两边状态不一致。
回滚后应重新检查阻断是否覆盖了原本预期的规则和路径范围。一个容易被忽略的细节是:某些平台对过期例外会保留“软放行”状态,回滚配置后需要显式清除残余的例外标记,否则阻断看起来恢复了,实际上并没有对每个分支生效。
例外治理的底线
回到开头的问题:门禁上线后例外不断累积,不是因为例外本身有错,而是因为例外缺少边界。检查和门禁的区分、例外的粒度、审批和到期的记录方式、复核失败后的恢复阻断、以及配置回滚路径,这些机制共同决定了例外是否可撤销。
风险接受是安全决策的一部分,但它不能替代修复。一条没有责任人和到期时间的例外,本质上是一次不做决策的决策。门禁的价值不在于永远说“不”,而在于每一次说“可以”的时候,都知道这句话的有效期到哪一天结束。
- injection
- it2021
- it2021.com





1F
门禁被口头确认绕过的坑太常见了
2F
路径排除要精确到文件 glob 才行
3F
误报理由别只写两个字,得说清楚
4F
SCA 例外千万别一刀切忽略 high 级