
get-shit-done 安全修复解析config-set/config-get 与 init 响应不再回显明文 API Key【免费下载链接】get-shit-doneA light-weight and powerful meta-prompting, context engineering and spec-driven development system for Claude Code by TÂCHES.项目地址: https://gitcode.com/GitHub_Trending/getshi/get-shit-done导读这是一篇针对 get-shit-done 仓库 PR #2997 安全修复changelog 条目 .changeset/merry-foxes-climb.md类型 Fixed的技术解读。该修复解决了一个真实泄漏隐患配置命令与初始化响应会把brave_search、firecrawl、exa_search等集成的 API Key 明文回显到终端、会话记录session transcripts与 Shell 历史中。本文以该变更为主线结合仓库源码讲解秘密键识别、掩码规则、CJS 与 SDK 双实现的一致性机制以及 init 场景中布尔可用性标记为何必须原样透传。读完后你将理解 get-shit-done 如何在磁盘保留明文、输出层强制脱敏的前提下组织密钥管理并能复现验证这一修复。一、变更背景SDK 移植过程中丢失的脱敏行为PR #2997 的问题在 sdk/src/query/secrets.ts 的文件头注释中被定性为security: SDK port lost masking behavior——即 SDKTypeScript 查询层在从 CJS 命令行实现迁移时丢失了配置面已有的密钥脱敏逻辑。修复前的泄漏路径很典型config-set/config-get这类命令的输出会进入 Agent 会话、workflow 输出与 shell history如果某个键被判定为敏感键且值为 API Key任何一次查询或写入都会把完整明文打印出来新迁移出的 SDKsdk/src/query/config-query.ts、sdk/src/query/config-mutation.ts在响应构造边界上没有做掩码导致同样的键在 SDK 调用路径上明文外泄。本次修复的做法不是少输一次密钥而是建立一套统一的脱敏原语并把 CJS 命令行与 SDK 两条调用链都接到同一套规则上。二、敏感键集合SECRET_CONFIG_KEYS 与 maskIfSecret 边界秘密键集合定义在 sdk/src/query/secrets.ts目前锁定为三个搜索/抓取类集成export const SECRET_CONFIG_KEYS: ReadonlySetstring new Set([ brave_search, firecrawl, exa_search, ]); export function isSecretKey(keyPath: string): boolean { return SECRET_CONFIG_KEYS.has(keyPath); }围绕该集合提供两个核心判断工具isSecretKey(keyPath)判断某个点号形式的配置键如brave_search是否属于敏感键maskIfSecretT(keyPath, value)只在键为敏感键时才掩码否则原样返回适合在响应构造边界直接套用export function maskIfSecretT(keyPath: string, value: T): T | string { return isSecretKey(keyPath) ? maskSecret(value) : value; }需要强调的设计意图同样写在 secrets.ts 注释中磁盘上的 config.json 值不变只有输出响应被脱敏。因为密钥明文本就存放在 config.json 中那是密钥的归宿CLI/SDK 只是绝不能把明文再送回终端。三、掩码规则****后四位的约定与示例统一的掩码实现maskSecret遵循一套可测试、可跨语言复制的约定定义于 sdk/src/query/secrets.tsexport function maskSecret(value: unknown): string { if (value null || value undefined || value ) return (unset); const s String(value); if (s.length 8) return ****; return **** s.slice(-4); }规则可以归纳为三条输入情况掩码输出说明null/undefined/ 空串(unset)明确表达未配置而非空值字符串长度 8****过短不留尾巴避免泄露可猜测信息字符串长度 ≥ 8****last-4仅暴露后 4 位便于人工区分多个 Key对应行为由单元测试锁定在 sdk/src/query/secrets.test.ts例如maskSecret(BSA-secret-key-abcd1234)→****1234maskSecret(short)→****maskSecret(null)→(unset)maskIfSecret(model_profile, quality)→quality非敏感键原样透传maskIfSecret(brave_search, BSA-1234567890)→****7890同一测试文件还断言了SECRET_CONFIG_KEYS集合内容被锁定locked防止有人误删或误加敏感键导致行为漂移。四、CJS 侧接入点config.cjs 如何保证命令行不泄密命令行的脱敏并非本次新增而是本次被找回来的对齐基准。CLI 侧通过config.cjs引入secrets.cjs见 get-shit-done/bin/lib/config.cjs。4.1 config-set写入前校验、输出时掩码cmdConfigSet在写入成功后立即判断敏感键config.cjs// Mask secrets in both JSON and text output. The plaintext is written // to config.json (thats where secrets live on disk); the CLI output // must never echo it. if (isSecretKey(keyPath)) { const masked maskSecret(parsedValue); const maskedPrev setConfigValueResult.previousValue undefined ? undefined : maskSecret(setConfigValueResult.previousValue); const maskedResult { ...setConfigValueResult, value: masked, previousValue: maskedPrev, masked: true, }; output(maskedResult, raw, ${keyPath}${masked}); return; }关键细节是新值与旧值previousValue都被掩码同时结果对象上追加masked: true标记且 JSON 与文本两种输出形态都走掩码分支避免--raw模式绕过脱敏。4.2 config-get读取响应同样永远呈现掩码形态cmdConfigGet在遍历点号路径拿到值之后命中敏感键时直接输出掩码结果config.cjs// Never echo plaintext for sensitive keys via config-get. Plaintext lives // in config.json on disk; the CLI surface always shows the masked form. if (isSecretKey(keyPath)) { const masked maskSecret(current); output(masked, raw, masked); return; }也就是说无论config.json里存的是多长的明文 Keyconfig-get brave_search反馈给用户的永远只会是****后四位形态。4.3 config-set 的取值校验前提另外值得了解的是config-set在写值前会做完整参数与枚举校验config.cjs包括拒绝只有键没有值的形式issue #3593 引入的防呆以及针对context、workflow.drift_action、ship.pr_body_sections等键的专项合法值检查。这些前置校验保证进入写盘与脱敏逻辑的值是可控的。五、SDK 侧修复在响应构造边界统一套用掩码本次修复的核心是把同一套规则移植进 SDK 查询层。三处调用点与 CJS 严格对齐5.1 config-query.ts读sdk/src/query/config-query.ts 引入maskIfSecret在读取响应的边界处L151-L154掩码// Mask plaintext for keys in SECRET_CONFIG_KEYS to match CJS behavior ... return { data: maskIfSecret(keyPath, current) };5.2 config-mutation.ts写sdk/src/query/config-mutation.ts 在写入响应中同时处理value与previousValue与 CJS 的config-set语义一致保证 SDK 调用方拿到的回执不会携带明文。5.3 init.ts初始化响应sdk/src/query/init.ts 对三个密钥键执行有条件的字符串掩码brave_search: typeof config.brave_search string ? maskIfSecret(brave_search, config.brave_search) : config.brave_search, firecrawl: typeof config.firecrawl string ? maskIfSecret(firecrawl, config.firecrawl) : config.firecrawl, exa_search: typeof config.exa_search string ? maskIfSecret(exa_search, config.exa_search) : config.exa_search,这正是 changelog 中init bundles only mask string values的实现落点。六、易踩坑点布尔可用性标记必须原样透传这是本变更里最微妙的一处设计体现在get-shit-done/bin/lib/init.cjs的 init 响应构造L846-L856// #2997: secret config keys may be either booleans (availability flags) or // string API keys (when user did gsd-tools config-set brave_search XXX). // Pass booleans through; mask string values so the init bundle never echoes // plaintext credentials. SDK init.ts mirrors this masking. brave_search: typeof config.brave_search string ? maskIfSecret(brave_search, config.brave_search) : config.brave_search,理解这个分支需要知道brave_search这类键在配置里存在两种合法形态——布尔可用性标记init 阶段会探测各搜索/抓取集成是否已配置可用 Keyinit.cjs 附近有 Detect Brave Search API key availability 等探测逻辑探测结果以布尔值写盘/上报供上层判断该集成能否使用字符串 API Key当用户执行config-set brave_search key时该键的值被替换为真正的密钥字符串。init 响应同时服务于两种消费方因此布尔值不能被塞进maskSecret布尔不是字符串脱敏会破坏true/false语义必须原样透传以保留可用性契约字符串值必须掩码否则一次 init 就会把整把 Key 打进会话上下文。这种按值类型分流的写法被 init.cjs 与 SDK 的 init.ts 双份镜像实现是防止为了修泄漏反而搞挂功能探测的关键护栏。七、双实现一致性从 TypeScript 单源生成 CJS 工件仓库里存在 CJSbin/lib与 TypeScript SDKsdk/src两套运行时若两份脱敏实现靠手写维护迟早会漂移。解决方式是单源生成 奇偶校验parity test逻辑单源为 sdk/src/query/secrets.ts生成脚本 sdk/scripts/gen-secrets.mjs 读取编译后的 ESM 输出用Function.prototype.toString()抽取isSecretKey/maskSecret/maskIfSecret的函数体并把SECRET_CONFIG_KEYS重建为常量声明最终写出 get-shit-done/bin/lib/secrets.generated.cjs生成文件头部明示GENERATED FILE — DO NOT EDIT并给出再生成命令cd sdk npm run gen:secretsget-shit-done/bin/lib/secrets.cjs 只是对生成物的一层薄转发re-export让历史调用方require(./secrets)无需改动。在此基础上sdk/src/query/secrets.test.ts 里的一组嵌套测试直接加载 CJS 工件做逐样本奇偶校验断言两边SECRET_CONFIG_KEYS集合完全一致且对代表性输入maskSecret输出逐字节相同。仓库根下另有 tests/secrets-generator.test.cjs 守护生成器的正确性保证生成—手写—测试闭环不被破坏。八、修复验证与迁移价值把本变更放到整个修复闭环中看它回答了三个可验证的问题敏感键集合是否完整SECRET_CONFIG_KEYS当前锁定为brave_search、firecrawl、exa_search三个键由 secrets.test.ts 断言锁定两条调用链是否都脱敏CJS 侧由 config.cjs 的config-set/config-get覆盖SDK 侧由 config-query.ts、config-mutation.ts 及对应测试如config-query.test.ts中 masks the response data for SECRET_CONFIG_KEYS 用例覆盖布尔可用性契约是否保留init 响应按值类型分流布尔透传、字符串掩码CJS 与 init.ts 行为镜像杜绝因脱敏误伤功能开关。这套机制的核心思想值得在同类工具中复制密钥明文只存放在磁盘配置文件这一处任何对外输出CLI、SDK 响应、init 上报在构造边界统一脱敏脱敏逻辑做成单源生成的可共享模块并用奇偶测试保证多运行时行为一致。它同时提醒迁移者把老代码逻辑迁到新语言/新模块时安全语义如脱敏是最容易被看起来无关紧要而丢掉的隐性契约——而 PR #2997 正是这样一个被追回来的契约。【免费下载链接】get-shit-doneA light-weight and powerful meta-prompting, context engineering and spec-driven development system for Claude Code by TÂCHES.项目地址: https://gitcode.com/GitHub_Trending/getshi/get-shit-done创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考