首页
/ get-shit-done 安全加固:secure-phase 不再为无威胁模型的存量阶段空签 SECURITY.md

get-shit-done 安全加固:secure-phase 不再为无威胁模型的存量阶段空签 SECURITY.md

2026-09-07 19:25:46作者:裘晴惠Vivianne

get-shit-done(GSD)在 v1.41.0 中通过 PR #3142 修复了问题 #3120:secure-phase 此前只要看到 threats_open: 0 就跳过审计并直接"盖章"一份干净的 SECURITY.md,却无法区分"所有计划期威胁均已闭环"和"阶段根本从未写过威胁模型"这两种截然不同的情况。本文以该修复为主线,拆解漏洞成因、register_authored_at_plan_time 标记的引入、retroactive-STRIDE 回溯审计模式,并结合 secure-phase 工作流gsd-security-auditor 智能体回归测试展开原理级解析。

GSD 的阶段安全门:从威胁建模到 SECURITY.md

get-shit-done 是一套"规范驱动开发"(spec-driven development)与上下文工程系统,其核心调度单元是 phase(阶段)。每个阶段在执行完毕后,必须通过安全门控才能进入验证(validate/verify)与收尾路由。这一门控体系由两条工作流协作完成:

  • plan-phase:在规划阶段就要求产出威胁模型。plan-phase.md 明确规定 Each PLAN.md must include a <threat_model> block.,并读取 workflow.security_enforcementworkflow.security_asvs_levelworkflow.security_block_on 三个配置键来设定审计强度(ASVS 级别与阻断阈值);
  • secure-phase:在阶段执行完成后,读取 PLAN.md 的 <threat_model> 威胁清单,派遣 gsd-security-auditor 逐条验证缓解措施是否真实存在于实现代码,最终产出/更新该阶段的 SECURITY.md。

secure-phase.md 是这一门控的完整执行契约。它的入口状态检测(Step 1)定义了三种输入态:

状态 判定条件 处理路径
State A 已存在 *-SECURITY.md 审计并复核既有缓解措施,做增量更新
State B 无 SECURITY.md,但 PLAN.md 与 SUMMARY.md 齐备 从阶段产物出发运行审计并新建 SECURITY.md
State C 无 SUMMARY.md(阶段未执行) 干净退出并提示先运行 /gsd:execute-phase

产出的 SECURITY.md 遵循统一模板,frontmatter 中包含 threats_openasvs_level 等字段,正文分为 Trust Boundaries、Threat Register、Accepted Risks Log、Security Audit Trail 与 Sign-Off 五个板块。

漏洞本质:threats_open: 0 携带的双重语义

问题 #3120 的根源在于 secure-phase Step 3 的短路规则被过度简化。修复前的工作流逻辑是:

只要 threats_open: 0,无论成因如何,一律跳到 Step 6 直接写入干净的 SECURITY.md。

但"威胁数为零"在现实中对应着两种语义截然不同的情形:

  • Case A(合法跳过):PLAN.md 在规划期确实写入了 <threat_model> 威胁清单,执行后逐条核实全部闭环(mitigated / accepted / transferred),threats_open: 0 意味着"审计通过";
  • Case B(静默空签):该阶段诞生于威胁建模成为规范(canonical)之前,PLAN.md 中压根没有 <threat_model> 块——此时 threats_open: 0 的真实含义是"从未建模、从未审计",而不是"安全"。短路规则却会让它直接跳过 Step 4/Step 5 的审计流程,"橡皮图章"式地签发一份零威胁的干净 SECURITY.md。

这正是一种典型的假阴性门控:门禁确实打开了,但打开的原因不是因为安全,而是因为没人检查过。changeset 记录 用一句尖锐的话概括了危害:legacy phases 会被 rubber-stamp(橡皮图章)——在零审计发生的情况下输出干净 SECURITY.md,让本应拦截的安全门形同虚设。

修复方案:用"规划期是否撰写了威胁模型"区分两种零威胁

PR #3142 的修复并未简单删除短路逻辑,而是引入了一个布尔标记来显式记录威胁登记的来源,从而把"零威胁"拆解为可判别、可追责的状态。整个修复落在 secure-phase.md 的三个步骤中。

Step 2c:追踪 register_authored_at_plan_time

在构建威胁登记表(threat register)的 Step 2c 中,工作流现在额外记录一个关键事实:

至少一个 PLAN 文件包含可解析的 <threat_model> 块,则置 register_authored_at_plan_time: true;若没有任何 PLAN 文件含 <threat_model> 块(即在正式威胁建模标准化之前撰写的 legacy 阶段),则置 false

这个标记回答了一个此前从未被显式记录的问题:当前的威胁登记表,究竟是"规划期精心设计后的事实",还是"压根不存在的东西"?

Step 3:短路门禁从单条件收紧为双条件

修复后的短路规则变为:

  • threats_open: 0 AND register_authored_at_plan_time: true允许跳过到 Step 6。规划期威胁全部经核实后闭环,跳过快照审计是合理的;
  • threats_open: 0 AND register_authored_at_plan_time: false禁止跳过。"因无规划导致的零威胁"不得签发干净 SECURITY.md,必须继续走到 Step 5;
  • threats_open > 0 → 进入 Step 4,向用户呈现实名威胁清单,等待"全部验证 / 全部接收 / 取消"三种处置。

Step 5:为 legacy 阶段引入 retroactive-STRIDE 回溯审计

对于被拦下的存量阶段,Step 5 派遣审计员的方式也发生了变化。审计员的约束随威胁登记来源而变化

register_authored_at_plan_time Step 5 审计模式 审计员任务
true 计划内模式 只验证已声明缓解措施是否存在,不扫描新威胁——登记表完备,逐一核对实现即可
false retroactive-STRIDE 模式 先从实现文件构建 STRIDE 威胁登记表,再验证缓解措施——阶段早于正式威胁建模,审计员必须从零重建登记表

retroactive(回溯式)STRIDE 是本次修复的灵魂:对 legacy 阶段而言,审计不再是"查缺补漏",而是从实现文件反推攻击面、按 STRIDE(Spoofing/Tampering/Repudiation/Information disclosure/Denial of service/Elevation of privilege)六类威胁重建登记表、再逐条核对缓解的三段式流程。这与 gsd-security-auditor 的角色定义 中"Does NOT scan blindly for new vulnerabilities"的常规约束形成鲜明对比——在回溯模式下,扫描新威胁恰恰是审计员的第一职责。

源码级验证:回归测试如何锁住该契约

修复不只是改了工作流文档,配套回归测试 bug-3120-secure-phase-empty-register.test.cjs 以结构断言(structural guard)的形式,把新契约固化为可执行的检查。测试直接读取 secure-phase.md 的原始文本并断言四个关键不变量:

  1. Step 2c 必须出现 register_authored_at_plan_time 标记——防止实现回退到不追踪来源的旧版;
  2. 短路条件必须是双条件联合——文本中必须存在 threats_open: 0 AND register_authored_at_plan_time,杜绝重新退化为只看威胁数的单条件;
  3. legacy 阶段必须被文档化为 retroactive-STRIDE 模式——工作流中必须出现 "retroactive"(回溯)语义,确保空登记表阶段有明确的兜底路径;
  4. Step 5 的审计员约束必须随模式变化——同时要求存在 "verify mitigations" 与 "retroactive" 两套并存的约束描述。

这组断言体现了该仓库一条重要的工程质量惯例:关键流程契约以 markdown 形式存在于可执行工作流中,并以正则/包含断言进行"文档即代码"式的测试。测试文件头部注释也直接点明了审计意图:防止 threats_open: 0 在"全部闭环(Case A)"与"从未建模(Case B)"之间被混淆。该修复随 v1.41.0 发布,发布说明 中列出了对应的变更条目。

深入审计员:从"验证存量"到"回溯构建"的能力切换

要理解 retroactive-STRIDE 的份量,需要先看清 gsd-security-auditor 的正常工作方式。gsd-security-auditor.md 定义了三种按 disposition(处置方式)区分的验证方法:

威胁处置 验证方法
mitigate 在缓解计划所引用的实现文件中 grep 缓解模式
accept 核对 SECURITY.md 的 Accepted Risks Log 中是否存在对应条目
transfer 核对转嫁文档(保险、供应商 SLA 等)是否存在

审计员被刻意设计为对抗性立场(adversarial stance):默认假设每个缓解措施都不存在,直到 grep 匹配证明它出现在正确位置;同时被要求警惕"一次匹配即全量缓解""结构上看着像校验了输入就算通过"等常见的放水模式。

这也解释了为什么修复需要的是 retroactive-STRIDE 而不是简单地在 Step 5 里跑一遍"常规审计":常规审计的输入是"完备的威胁登记表 + 逐条核对",而 legacy 阶段根本没有登记表。让审计员在缺输入的情况下重复"验证缓解"是无意义的——先构建、后验证,才是对旧阶段负责任的审计语义。

门禁语义与 SECURITY.md 的落地形态

修复最终的落点仍是 Step 6 的 SECURITY.md 写入与门禁判定。模板中 Threat Register 的 disposition 取值被严格限定为 mitigate / accept / transfer,而 frontmatter 中的 threats_open 是贯穿整个工作流的唯一真值来源。修复后,即便 Step 3 放行或 Step 5 审计通过,Step 6 的执行门禁 依然保留最后一道防线:

若用户未接收风险且威胁未全部验证闭环(threats_open > 0),输出 GSD > PHASE {N} SECURITY BLOCKED禁止阶段推进,不回显任何 next-phase 路由。

只有在 threats_open: 0 时,工作流才会走向 Step 8 的成功路由(/gsd:validate-phase / /gsd:verify-work),给出 THREAT-SECURE 的结果 banner。

实操:如何复现与触发回溯审计

对使用者而言,本次修复是一个无感但关键的行为变化。结合命令定义,掌握以下几点即可在实践中触发并观察该机制:

  • 命令形态/gsd:secure-phase [phase number],缺省时指向最近一个已完成的阶段;
  • 触发回溯审计的条件:目标阶段存在 PLAN/SUMMARY 产物且无 SECURITY.md(State B),同时其 PLAN.md 不含 <threat_model> 块——此时你会看到审计员以 retroactive-STRIDE 模式从实现文件重建威胁登记表,而不是快速签发干净文档;
  • 快速鉴别当前阶段类型:直接查看对应 PLAN.md 中是否出现 <threat_model> 区块。无区块即 legacy 阶段,是 #3120 修复后受保护的场景;
  • 安全门控可配置:若确认某个项目不需要强制安全建模,可在配置中将 workflow.security_enforcement 设为 false(默认缺失即视为启用),secure-phase 会在 Step 0 据此干净退出;ASVS 级别与阻断条件分别由 workflow.security_asvs_levelworkflow.security_block_on 控制;
  • 非 Claude 运行时:文本模式(--text 标志或 workflow.text_mode: true)下,Step 4 的用户选择以纯文本编号列表呈现,替代 AskUserQuestion,兼容 Codex、Gemini CLI 等运行时。

小结

#3120 的修复表面上是给一个短路条件多加了一个布尔判断,实质上是把安全审计系统的信任前提显式化:GSD 不再默认"威胁列表为空 = 安全",而是追问一句"这份空列表到底是从严谨的规划中来,还是从未被写过"。register_authored_at_plan_time 这一标记把威胁登记的来源纳入门控决策,retroactive-STRIDE 则为早于威胁建模规范的存量阶段提供了从实现反推攻击面的补救路径,而配套回归测试确保这一契约在后续迭代中不会被悄悄退化。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388