
【免费下载链接】gsd-coreGit. Ship. Done - Core项目地址https://gitcode.com/gh_mirrors/ge/gsd-core点击查看免费下载导读本文解析 gsd-core 的安全变更 #3589此前直接调用relPlanningPath、planningPaths或ContextEngine并显式传入 workstream 参数的 SDK 调用方缺少路径穿越path traversal校验一个形如../../../outside的 workstream 名称会经posix.join(.planning, workstreams, name)把规划操作路由到.planning/workstreams/name子树之外。修复方案把共享的validateWorkstreamName策略下沉到relPlanningPath内部让所有消费方在同一接缝处以失败即关闭fail closed的方式被拦截同时保留 #2791 契约下环境变量来源 workstream 的静默根目录回退。读完本文你将掌握该漏洞的成因与利用路径、统一校验策略的完整规则、环境变量的特殊契约以及仓库内对应的源码与回归测试证据。背景Planning Workspace 接缝与规划路径投影在 gsd-core 中.planning/目录是全部规划工件STATE.md、ROADMAP.md、phases/、requirements等的根目录。当启用 workstream 时规划数据被路由到.planning/workstreams/name/子树下。负责从项目/工作流上下文投影出具体.planning路径的模块是 Planning Workspace 接缝其 TypeScript 权威实现位于 src/planning-workspace.cts编译产物为gsd-core/bin/lib/planning-workspace.cjs。规划路径解析的策略优先级在 docs/adr/0006-planning-path-projection-module.md 中被明确为显式 workstream 环境变量 workstream 环境变量 project 根目录该 ADR已于 2026-05-09 被接受确立了如下决策helpers.planningPaths(projectDir, workstream?)是 SDK 规划路径投影的规范接口所有查询/初始化处理器initExecutePhase、initPlanPhase、initPhaseOp、initMilestoneOp必须消费planningPaths(...).planning而非直接做relPlanningPath拼接SDK 项目级规划作用域是.planning/project绝不使用.planning/projects/project。planningPaths在 src/planning-workspace.cts 中以planningDir解析出的 base 为根投影出state、roadmap、project、config、phases、requirements、debug、quick、todos等键function planningPaths(cwd: string, ws?: string | null): PlanningPaths { const base planningDir(cwd, ws); return { planning: base, state: path.join(base, STATE.md), roadmap: path.join(base, ROADMAP.md), project: path.join(base, PROJECT.md), config: path.join(base, config.json), phases: path.join(base, phases), requirements: path.join(base, REQUIREMENTS.md), debug: path.join(base, debug), quick: quickDirFrom(base), todos: todosDir(cwd), // 刻意保持根作用域 }; }正是这个投影链路成为 #3589 修复的接缝seam。漏洞分析未校验的 workstream 参数如何造成路径穿越脆弱的拼接点变更集明确描述了漏洞的成因直接向relPlanningPath、planningPaths或ContextEngine传入 workstream 参数的 SDK 调用方此前没有任何路径穿越闸门。传入的值会原样流经posix.join(.planning, workstreams, name)由于name未经校验一个包含../的值会直接越过.planning/workstreams/子树。例如name ../../../outside // 解析结果落在 .planning/workstreams/ 之外规划操作被路由到仓库外部目录这意味着依赖这些 SDK 接口的上层逻辑在解析规划工件STATE、ROADMAP、phases 等时可能读取、写入或影响项目目录之外的路径——这是典型的路径穿越安全缺陷。受影响入口按变更集描述受影响的是三类直接 SDK 调用方relPlanningPath相对规划路径拼接助手ADR-0006 已将其标注为应避免的直接拼接反模式本次修复在其内部补齐了校验闸门planningPaths规划路径投影接口ContextEngine上下文引擎中与规划路径解析相关的消费方。这三者此前共享同一个缺失显式传入的 workstream 参数没有经过策略校验即参与路径拼接。修复方案共享校验策略在接缝处 fail closed校验策略的来源修复的核心是在relPlanningPath内部运行共享的validateWorkstreamName策略。该策略的权威实现在 src/workstream-name-policy.cts并被 src/active-workstream-store.cts 以同名函数转发使用// src/workstream-name-policy.cts export const ACTIVE_WORKSTREAM_RE /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/; export function validateWorkstreamName(name: string | null | undefined): boolean { return isValidActiveWorkstreamName(name); }validateActiveWorkstreamName的完整判定分两步规范化输入String(name ?? ).trim()空串返回reason: empty形状校验先经hasInvalidPathSegment拒绝任何路径分隔符、裸.、..及包含..的序列再用ACTIVE_WORKSTREAM_RE校验字符集合。hasInvalidPathSegmentsrc/workstream-name-policy.cts的判定逻辑export function hasInvalidPathSegment(name: string | null | undefined): boolean { const value String(name ?? ); return /[/\\]/.test(value) || value . || value .. || value.includes(..); }完整合法性规则综合 src/workstream-name-policy.cts 的源码与 tests/workstream-name-policy.test.cjs 的断言workstream 名称的校验规则可归纳为下表类别规则示例允许字母数字、连字符、下划线、点号且必须以字母数字开头alpha-1、feature_x、v1.2拒绝空值 / 纯空白、 拒绝路径分隔符正斜杠或反斜杠foo/bar、foo\bar拒绝点号穿越序列..、ws..traversal、../../etc拒绝空白字符ws name with spaces拒绝其他特殊字符alpha beta之外的所有非白名单字符测试中的典型断言tests/workstream-name-policy.test.cjsassert.equal(isValidActiveWorkstreamName(alpha-1), true); assert.equal(isValidActiveWorkstreamName(ws..traversal), false); assert.equal(isValidActiveWorkstreamName(alpha beta), false);fail closed 的含义每个消费方都在同一接缝处失败即关闭意味着一旦relPlanningPath收到不合法的显式 workstream 名称路径解析立即失败抛出/拒绝而不是尽力解析或静默落到其他目录。相比过去未校验 → 拼接出越界路径的行为修复后的语义是拒绝比放行更安全任何无法通过共享策略的名称都不得参与.planning路径的构造。这也与 src/active-workstream-store.cts 中setActiveWorkstream的既有行为一致——后者在写入前同样用validateWorkstreamName抛出错信息Invalid workstream name: must be alphanumeric, hyphens, underscores, or dots可见同一策略在多个接缝处复用是仓库一贯的做法。环境变量契约#2791Env 来源 workstream 静默回退根目录变更集特别强调了一个需要保留的既有契约环境变量来源的 workstream 继续静默回退到根.planning/。也就是说本次修复只针对显式传入的 workstream 参数不改变 #2791 契约下环境变量的行为。其机制是planningPaths在把 env 来源的 workstream 传给relPlanningPath之前先将其过滤为null。此时planningDir的解析退化为项目/根作用域// src/planning-workspace.cts — resolveEnvWorkstream function resolveEnvWorkstream(): string | null { const value process.env[GSD_WORKSTREAM]?.trim(); return value || null; // 空白/空值 → null → 根目录 }planningDir中ws null时不会拼接workstreams/name段src/planning-workspace.ctslet base path.join(cwd, .planning); if (project) base path.join(base, project); if (ws) base path.join(base, workstreams, ws); return base;因此非法/空白的 env workstream 会退化为根目录而不是抛错——这是 #2791 契约刻意保留的静默行为。tests/adr-612-bracket-phase-counting.test.cjs 用GSD_WORKSTREAM ../evil验证了这一点extractCurrentMilestone必须正常返回内容而非抛错——穿越段在 env 入口被降级而非逃逸process.env.GSD_WORKSTREAM ../evil; const out rp.extractCurrentMilestone(ROADMAP, dir); assert.equal(typeof out, string, must return content, not throw);同样的降级语义也体现在 tests/planning-workspace.test.cjs纯空白环境变量会被规范化为null最终解析到根.planning/#4462回归覆盖。纵深防御planningDir 的 BAD_SEGMENT 与更广代码库的闸门relPlanningPath内的共享校验并非仓库唯一的防护层。planningDirrelPlanningPath/planningPaths的共同基底在 src/planning-workspace.cts 中还有一层独立的BAD_SEGMENT检查// Reject path separators and traversal components in project/workstream names const BAD_SEGMENT /[/\\]|\.\./; if (project BAD_SEGMENT.test(project)) { throw new Error(GSD_PROJECT contains invalid path characters: ${project}); } if (ws BAD_SEGMENT.test(ws)) { throw new Error(GSD_WORKSTREAM contains invalid path characters: ${ws}); }这一层针对GSD_PROJECT/GSD_WORKSTREAM环境变量与显式参数抛出形如invalid path characters的错误。对应测试见 tests/planning-workspace.test.cjsassert.throws(() planningDir(cwd, null, ../../etc), /invalid path characters/); assert.throws(() planningDir(cwd, foo/bar, null), /invalid path characters/); assert.throws(() planningDir(cwd, foo\\bar, null), /invalid path characters/);以及 tests/init-debug.test.cjs它专门断言planningPaths.debug键不削弱穿越闸门test(planningPaths.debug does not weaken the traversal guard (row E4), () { assert.throws(() planningPaths(tmpDir, ../../etc), /invalid path characters/); assert.throws(() planningPaths(tmpDir, foo/bar), /invalid path characters/); });在更广的代码库层面workstream 相关命令同样拒绝了穿越形态的名称。tests/workstream.test.cjs 的path traversal rejection测试组覆盖了三类入口——--ws标志、GSD_WORKSTREAM环境变量、cmdWorkstreamSet——并使用同一批恶意名称const maliciousNames [ ../../etc, // 父目录穿越 ../foo, // 父目录穿越 ws/../../../passwd, // 嵌入穿越段 a/b, // 路径分隔符 ws name with spaces,// 空白字符 .., ., // 点号穿越 ws..traversal, // 点号序列 ];每个入口都断言失败且错误信息包含Invalid workstream name。这印证了变更集每个消费方都在同一接缝处失败即关闭的设计——不同入口共享同一策略回归面被收敛。对 SDK 调用方与维护者的实践建议基于本次修复的接缝设计与仓库既有约定可总结出以下可落地的实践原则显式参数必须校验环境变量允许降级凡是调用方显式传入的 workstream 名称一律通过validateWorkstreamNamesrc/workstream-name-policy.cts以 fail closed 方式拒绝非法值只有 env 来源GSD_WORKSTREAM才允许按 #2791 契约静默回退到根.planning/。优先使用投影接口而非手写拼接遵循 docs/adr/0006-planning-path-projection-module.md查询/初始化处理器应消费planningPaths(...).planning避免新增relPlanningPath式的手工path.join站点防止策略漂移。新增路径键复用共享助手仓库用quickDirFrom、todosDirFrom等共享助手消除同一路径两个拼接点的缺陷模式见 src/planning-workspace.cts新增planningPaths键时应遵循同样做法。用测试钉住闸门穿越防护应同时覆盖拒绝路径与降级路径两类行为——前者如../../etc必须抛错后者如GSD_WORKSTREAM../evil必须静默降级参考 tests/adr-612-bracket-phase-counting.test.cjs两类语义缺一不可。结语#3589 是一次典型的接缝收敛安全修复把分散在 SDK 直接调用方之间的路径穿越风险统一收口到relPlanningPath内部对共享validateWorkstreamName策略的调用使所有消费方在同一处 fail closed同时通过显式参数校验 env 参数降级的双轨设计完整保留 #2791 契约的向后兼容性。结合planningDir的BAD_SEGMENT闸门、active-workstream-store的写入校验以及覆盖--ws/GSD_WORKSTREAM/ 命令入口的回归测试仓库在规划路径投影这一关键安全边界上形成了层次完整的纵深防御。文中涉及的关键源码与测试路径变更记录 .changeset/archived/3589-planning-paths-workstream-validation.md、决策记录 docs/adr/0006-planning-path-projection-module.md、策略实现 src/workstream-name-policy.cts、投影实现 src/planning-workspace.cts、转发层 src/active-workstream-store.cts、回归测试 tests/workstream-name-policy.test.cjs、tests/planning-workspace.test.cjs、tests/init-debug.test.cjs、tests/workstream.test.cjs 与 tests/adr-612-bracket-phase-counting.test.cjs。赞分享【免费下载链接】gsd-coreGit. Ship. Done - Core项目地址https://gitcode.com/gh_mirrors/ge/gsd-core点击查看免费下载相关推荐get-shit-done SDK 安全加固剖析relPlanningPath 对工作流名的路径穿越校验Issue 3589get shit done SDK 安全加固剖析relPlanningPath 对工作流名的路径穿越校验Issue 3589 本文基于仓库中的变更集文档人工智能AI 应用提示工程开发工具工作流自动化AI Agentgsd-core Workstream 命名规范化CJS 与 SDK 双层的名字校验、路径穿越拦截与活动指针安全gsd core Workstream 命名规范化CJS 与 SDK 双层的名字校验、路径穿越拦截与活动指针安全 本篇围绕 gsd core 的一次缺陷修复get-shit-done 工作流名称规范化与路径穿越防护CJS/SDK 双层校验一致性加固解析get shit done 工作流名称规范化与路径穿越防护CJS/SDK 双层校验一致性加固解析 本文围绕 get shit doneTÂCHES 出品的轻人工智能AI 应用提示工程开发工具工作流自动化AI Agent创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考