【免费下载链接】gsd-core
Git. Ship. Done - Core
本文以
.changeset/archived/fix-3120-secure-phase-empty-register.md记录的变更(PR #3142,关闭 issue #3120)为核心,剖析 GSD Core 安全阶段工作流secure-phase中一个隐蔽的审计绕过缺陷:当threats_open: 0时,工作流无法区分"所有威胁都已缓解"与"根本没有编写任何威胁模型"。通过register_authored_at_plan_time标记与 retroactive-STRIDE 模式,修复后遗留阶段(在<threat_model>块成为规范之前编写的 PLAN)不再被"盖章放行"干净的空 SECURITY.md。读完本文,你将掌握 secure-phase 的完整状态机、短路门控规则、审计器约束差异,以及如何用仓库源码与测试验证这一安全契约。
一、问题本质:threats_open: 0的双重语义
1.1 secure-phase 工作流在做什么
/gsd-secure-phase是 GSD Core 中"对一个已完成阶段进行威胁缓解验证"的复核命令,其完整执行契约定义在 gsd-core/workflows/secure-phase.md,命令入口在 commands/gsd/secure-phase.md。它的职责是:
- 从该阶段的
PLAN.md中提取<threat_model>块(信任边界 + STRIDE 威胁登记簿); - 从
SUMMARY.md中提取## Threat Flags(执行期新出现的攻击面); - 校验每个威胁的处置(
mitigate/accept/transfer)是否已在实现中落实; - 更新或生成
{phase}-SECURITY.md,并在存在未缓解阻断性威胁时阻止阶段推进。
工作流首先检测输入状态(Step 1):
| 状态 | 判定条件 | 行为 |
|---|---|---|
| State A | SECURITY.md已存在 | 审计并复核既有缓解措施 |
| State B | 无SECURITY.md,但PLAN.md与SUMMARY.md存在 | 从阶段产物出发生成安全文档 |
| State C | 无SUMMARY.md(阶段未执行) | 直接退出并提示先运行/gsd-execute-phase {N} |
1.2 旧短路逻辑的漏洞
在 PR #3142 修复之前,secure-phase工作流的 Step 3 威胁分类存在一个条件过宽的短路:只要threats_open: 0,就直接跳过审计、跳到 Step 6 写出一份"干净"的SECURITY.md。
threats_open: 0在数学上看起来是无害的——没有未缓解威胁。但它在语义上是歧义的:
- Case A(合法的零):计划期编写的
<threat_model>中的所有威胁都已被实现代码缓解或记录为接受/转移风险,审计确实无话可说; - Case B(虚假的零):阶段是遗留阶段,其 PLAN 编写于
<threat_model>块成为规范之前,PLAN 中根本没有任何威胁模型。此时登记簿为空是因为"从未写过",而非"全部处理完"。
旧逻辑把 Case B 也当作 Case A 处理:零威胁 → 直接写一份threats_open: 0的干净SECURITY.md。这正是 changeset 中 "rubber-stamps SECURITY.md"(橡皮图章式盖章放行)一词的含义——审计零执行,却产出零威胁的结论,整个安全门形同虚设。
二、修复方案:给"登记簿来源"加上可追溯标记
2.1 Step 2c:追踪register_authored_at_plan_time
修复的核心是在工作流的Step 2c(构建威胁登记簿)引入一个新的布尔标记register_authored_at_plan_time,其判定规则(gsd-core/workflows/secure-phase.md):
- 若该阶段至少一个PLAN 文件包含可解析的
<threat_model>块 →register_authored_at_plan_time: true; - 若没有任何 PLAN 文件含
<threat_model>块(即遗留阶段,早于正式威胁建模规范)→register_authored_at_plan_time: false。
与此同时,Step 2c 中每个威胁的登记簿条目形状为:{ threat_id, category, component, severity, disposition, mitigation_pattern, files_to_check }——severity字段是后来 #1626 引入的按威胁严重度门控的基础,审计器正是依据它结合block_on阈值计算threats_open的。
2.2 Step 3:短路门控从单条件变为双条件
修复后,Step 3 的短路规则被改写为以下四个分支(这是 changeset 与工作流原文共同确立的核心契约):
| 条件 | 行为 |
|---|---|
threats_open: 0且register_authored_at_plan_time: true且asvs_level == 1 | 直接跳到 Step 6。登记簿完整且 L1 grep 深度足够,无阻断性威胁剩余 |
threats_open: 0且register_authored_at_plan_time: true且asvs_level >= 2 | 不跳过,进入 Step 5 派发审计器。初步分类是 grep 级(L1 深度),对 L2/L3 不足——跳过会破坏 ASVS 等级在"干净阶段"上的缩放 |
threats_open: 0且register_authored_at_plan_time: false | 不跳过,进入 Step 5 的retroactive-STRIDE 模式——空登记簿不能盖章放行干净 SECURITY.md |
threats_open > 0 | 进入 Step 4,向用户呈现威胁处理计划 |
关键点在于:"零威胁"本身不再构成跳过的充分条件,只有"零威胁 + 登记簿确实来源于计划期建模"同时成立,且 ASVS 等级仅为 L1 时才允许短路。两个维度(威胁数量 × 登记簿来源)现在都被显式检查。
三、retroactive-STRIDE 模式:从实现文件反推登记簿
3.1 审计器的两种约束
Step 5 负责派发gsd-security-auditor子代理,其约束随登记簿来源(register_authored_at_plan_time)而变化:
- 计划期登记簿(
true):审计器只验证已声明的缓解措施是否存在于实现中,不扫描新威胁——登记簿视为完整; - retroactive-STRIDE 模式(
false):审计器先从实现文件构建 STRIDE 登记簿,再验证缓解措施。因为阶段在正式威胁建模之前编写,审计器必须从零构造登记簿。
retroactive-STRIDE 模式在审计器 agent 的analyze_threats步骤中也有对应处理:当 PLAN.md 中没有<threat_model>块需要追溯构建登记簿时,审计器需要为每个自行构造的威胁按影响 × 可能性分配严重度(agents/gsd-security-auditor.md)。
3.2 审计器如何工作
gsd-security-auditor是 secure-phase 工作流唯一允许派发的子代理类型(工作流<available_agent_types>中明确不允许回退到 general-purpose)。其关键设计(agents/gsd-security-auditor.md):
- 只读契约:工具集为
Read / Bash / Glob / Grep,不含 Write 与 Edit(#2119 单写者契约,SECURITY.md 由编排器写回);实现文件只读,审计器只返回结构化判决,不修补实现; - 对抗立场:默认假设每个缓解措施都不存在,直到 grep 匹配证明其在正确位置存在;
- 按处置类型验证:
mitigate→ 在缓解计划引用的文件中 grep 缓解模式;accept→ 核对 SECURITY.md 已接受风险日志;transfer→ 核对转移文档(保险、供应商 SLA 等)存在; - 验证深度随
asvs_level缩放:L1 只查模式存在(grep 级);L2 验证缓解措施确实针对威胁向量且位于正确边界;L3 端到端追踪数据流、检查边界情况与顺序,确认无旁路。
3.3threats_open的严重度感知计算
审计器返回的threats_open是 SECURITY.md frontmatter 中的门控字段,其计算规则(严重度序:critical > high > medium > low):
threats_open= 状态为 OPEN且严重度等级 ≥block_on等级的威胁数;block_on: none→ 永不阻断(恒为 0);block_on: low→ 所有 OPEN 威胁都阻断;block_on: high(默认)→ 仅 high 与 critical 阻断;- 低于阈值的 OPEN 威胁记为open — below {block_on} threshold (non-blocking),不计入
threats_open; - 缺失严重度的失败关闭策略:OPEN 威胁若无严重度或严重度无法解析(如早于 Severity 列的遗留登记簿),按
critical处理并计入threats_open——绝不静默丢弃未分级的 OPEN 威胁。
四、完整流程回顾:Step 0 到 Step 8
结合 gsd-core/workflows/secure-phase.md 的原始流程,修复后的 secure-phase 全链路为:
- Step 0 初始化:解析
phase_dir等参数;通过gsd_run query config-get读取workflow.security_asvs_level(默认1)与workflow.security_block_on(默认high);通过loop render-hooks verify:post解析能力钩子——若无ref.skill == "secure-phase"的活动步骤钩子则退出并提示 "Security enforcement disabled. Enable via /gsd:settings."; - Step 1 输入状态检测:判定 State A / B / C;
- Step 2 发现:2a 读 PLAN 提取
<threat_model>;2b 读 SUMMARY 的## Threat Flags;2c 构建威胁登记簿并设置register_authored_at_plan_time; - Step 3 威胁分类:按上文的四分支短路规则处理;
- Step 4 呈现威胁计划:文本模式(
workflow.text_mode: true或--text标志)下用纯文本编号列表替代AskUserQuestion,提供三个选项:验证全部 OPEN 威胁 / 全部接受并记录到已接受风险日志 / 取消; - Step 5 派发审计器:打印
◆ Spawning security auditor...(子代理运行约 1–5 分钟,无输出属正常);按运行时类型解析派发(#2508),模型参数在"inherit"或为空时省略(#2517);处理三种返回## SECURED/## OPEN_THREATS/## ESCALATE; - Step 6 写入/更新 SECURITY.md:State B 从 gsd-core/templates/SECURITY.md 模板生成;State A 追加审计日志表(Threats found / Closed / Open);若所有选项用尽后
threats_open > 0,触发强制执行门:输出GSD > PHASE {N} SECURITY BLOCKED,明确Do NOT emit next-phase routing,阻止阶段推进; - Step 7 提交:
gsd_run query commit提交 SECURITY.md; - Step 8 结果与路由:成功(
threats_open: 0)时输出GSD > PHASE {N} THREAT-SECURE,并给出/gsd-validate-phase {N}与/gsd-verify-work {N}的后续路由建议。
五、仓库证据链:工作流、模板、命令与回归测试
5.1 SECURITY.md 模板的结构
gsd-core/templates/SECURITY.md 是 State B 生成阶段安全文档的骨架,其 frontmatter 包含门控字段:phase、slug、status、threats_open(注释注明其为"达到或超过workflow.security_block_on严重度的 OPEN 威胁计数,即阻断门")、asvs_level、created。正文包含五个部分:
- Trust Boundaries:信任边界表(边界、描述、穿越数据);
- Threat Register:
Threat ID / Category / Component / Severity / Disposition / Mitigation / Status七列登记表,附状态、严重度、处置三项语义注释; - Accepted Risks Log:已接受风险日志("Accepted risks do not resurface in future audit runs.");
- Security Audit Trail:审计轨迹表(审计日期、威胁总数、已关闭、开放、执行者);
- Sign-Off:签署清单,含
threats_open: 0确认与status: verified设置。
5.2 命令入口与文档
- commands/gsd/secure-phase.md:frontmatter 声明
name: gsd:secure-phase、allowed-tools列表(Read / Write / Edit / Bash / Glob / Grep / Agent / AskUserQuestion,注意编排器本身持有 Write 权限,与审计器只读形成对照)、requires: [phase];<objective>明确三状态模型; - docs/COMMANDS.md:
/gsd-secure-phase的三种运行模式(SECURITY.md 存在则复核 / 无 SECURITY.md 但 PLAN 有威胁模型则从产物生成 / 阶段未执行则退出),默认审计最后完成的阶段,产物为{phase}-SECURITY.md; - docs/explanation/security-model.md:解释 GSD Core 的纵深防御安全模型,secure-phase 属于其中"对已完成阶段验证威胁缓解"的验证层。
5.3 回归测试:bug-3120折叠用例
本次修复的直接回归保障位于 tests/secure-phase.test.cjs 末尾折叠的folded:bug-3120-secure-phase-empty-register测试组(注释中完整复述了问题与修复语义)。该组对gsd-core/workflows/secure-phase.md的文本内容做结构性断言,验证四项契约:
- Step 2c 追踪
register_authored_at_plan_time:工作流源码必须包含该标识; - Step 3 短路要求双条件:必须包含
threats_open: 0 AND register_authored_at_plan_time门控; - 遗留阶段记录 retroactive-STRIDE 模式:工作流必须包含
retroactive/Retroactive字样; - Step 5 审计器约束随模式变化:必须同时包含 "Verify mitigations"(计划期)与 "Retroactive"(追溯期)两种约束表述。
同文件还包含对审计器 agent 的工具集断言(必须含 Read/Bash/Glob/Grep,必须不含 Write/Edit,即 #2119 单写者契约)、对 config.json 默认值的断言(workflow.security_enforcement: true、workflow.security_asvs_level: 1、workflow.security_block_on: "high")、以及对 VALIDATION.md 模板Threat Ref与Secure Behavior列的断言。这些测试遵循仓库 "source-text-is-the-product" 的测试规则:工作流 / agent / 命令 Markdown 文本本身就是运行时加载的契约,测试文本内容即测试部署后的契约。
六、配置参数与实操建议
secure-phase 相关的三个核心配置项(默认值来自 gsd-core/templates/config.json,与测试断言一致):
| 配置键 | 默认值 | 语义 |
|---|---|---|
workflow.security_enforcement | true | 安全强制开关;置为false时工作流会退出并提示通过/gsd:settings开启 |
workflow.security_asvs_level | 1 | 验证深度(L1 grep 存在性 / L2 边界位置 / L3 端到端追踪),也决定threats_open: 0时是否允许短路 |
workflow.security_block_on | high | 阻断阈值(critical>high>medium>low>none),只有达到或超过该严重度的 OPEN 威胁计入threats_open |
实际使用建议:
- 运行
/gsd-secure-phase {N}前先确认阶段已执行(State C 会干净退出并给出引导); - 对新项目,
gsd-planner在security_enforcement启用时要求每个 PLAN 必须包含<threat_model>块(agents/gsd-planner.md 中的强制要求),因此新阶段天然满足register_authored_at_plan_time: true; - 对历史遗留阶段,本次修复意味着不再能依赖空 PLAN 快速过关——审计器会以 retroactive-STRIDE 模式从实现文件重建登记簿,这是刻意的安全代价,而非缺陷;
- 若阶段推进被
threats_open > 0阻断,只能通过补实现缓解措施后重跑/gsd-secure-phase {N},或在 SECURITY.md 中记录已接受风险后重跑——工作流明确禁止在此状态下发出下一阶段路由。
七、总结
PR #3142 对 issue #3120 的修复,本质上是一次语义消歧:把threats_open: 0从单一布尔条件拆解为"威胁数量"与"登记簿来源"两个独立维度。register_authored_at_plan_time使工作流能够回答"这个零威胁结论是审计出来的,还是从未审计过的"这一问题,从而堵住遗留阶段绕开安全审计的路径。整个契约由工作流原文、审计器 agent 定义、SECURITY.md 模板与折叠回归测试共同锚定,任何人修改 secure-phase 的行为都会在这些结构断言中现形。
【免费下载链接】gsd-core
Git. Ship. Done - Core
相关推荐
get-shit-done 如何用 /gsd-secure-phase 对已完成 phase 做威胁验证并配置 ASVS 级别
get shit done 如何用 /gsd secure phase 对已完成 phase 做威胁验证并配置 ASVS 级别 在 get shit done(
人工智能AI 应用提示工程开发工具工作流自动化AI Agentgsd-core W002 误报修复详解:/gsd:health 为何不再为已归档 Phase 报错
gsd core W002 误报修复详解:/gsd:health 为何不再为已归档 Phase 报错 本文围绕 gsd core 仓库中的一个变更集记录(cha
gsd-core `/gsd-discuss-phase` 模式路由修复:让 `workflow.discuss_mode: assumptions` 在 shim-only 安装下也能被正确遵守
gsd core /gsd discuss phase 模式路由修复:让 workflow.discuss_mode: assumptions 在 shim o
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考