在做 #5048(PR #5572)时实测发现,顺手记录。 ## 现象 `packages/core/src/logger.ts` 的 `redactSensitive` 递归遍历 meta,判定条件是**子串包含**: ```js private redactSensitive(obj: any): any { ... const lower = key.toLowerCase(); if (this.config.redact.some((p: string) => lower.includes(p.toLowerCase()))) { redacted[key] = '***REDACTED***'; } ``` 默认脱敏表(`logger.ts:97`,与 `packages/spec/src/system/logging.zod.ts:71` 的 schema 默认值一致)是 `['password', 'token', 'secret', 'key']`。于是任何**名字含这些子串**的字段都会被整块替换,不问值是什么: - `keys`(含 `key`) - `keyword` / `keywords`、`keyboard`、`monkey` - `tokenizer`、`tokens`(计数!) - `secretary` 实测(`format: 'json'`,值是一个普通的字符串数组): ``` log.warn('…', { issues: [{ code: 'unrecognized_keys', keys: ['visibleIf'], path: ['nodes', 0] }] }) → {"…","issues":[{"code":"unrecognized_keys","keys":"***REDACTED***","path":["nodes",0]}]} ``` `keys` 里根本没有秘密,但读者拿到的是 `***REDACTED***` —— 而且这个字样会让读者以为**确实有**一个秘密被挡住了,比字段缺失更误导。 ## 为什么是 finding 而不是 bug 全仓 grep 过,**目前没有任何 in-tree 的 `logger.*` 调用**在 meta 里放这类字段名(#5048 差点成为第一个 —— 那条路已经在 PR #5572 里绕开了:字段改名叫 `unrecognized`,并写了 pin 测试固定「原样转发会被脱敏」这个事实)。所以今天没有用户会撞上,归观察类。 但 `ObjectLogger` 是公开导出的,`redact` 也是 `LoggerConfig` 的公开配置项,host 侧插件自己写 `logger.info(..., { keys: [...] })` 就会撞上,而且撞上时是**静默**的 —— 没有任何提示说脱敏器动过手。 ## 取舍(不预设结论) 子串匹配不是笔误,它是为了一网打尽 `apiKey` / `secretKey` / `privateKey` / `accessToken` 这些真实拼法 —— 换成精确匹配会漏掉它们,那更危险。所以这不是「改成 equals 就好」,而是需要维护者定一个方向,几个选项: - **A. 保持子串,只在词边界上匹配**:`key` 匹配 `apiKey`/`api_key`(camelCase / snake_case 分词后是独立词),但不匹配 `keys`/`monkey`。能同时保住覆盖面和精度,实现是分词而不是 `includes`。 - **B. 保持子串,加一个例外表**(`keys`、`keyword`、…)。便宜,但例外表永远追不齐,是治症状。 - **C. 值敏感**:只脱敏标量,不脱敏数组/对象。恰好能救 `keys: [...]`,但 `apiKeys: ['sk-…']` 就漏了 —— 方向不对。 - **D. 不改,只记入文档**,让作者自己避开这些字段名。等于把一个静默数据丢失的陷阱转成文档义务。 倾向 A(词边界),因为它在「不漏真秘密」和「不吃普通字段」之间不用二选一;但这动的是脱敏语义,属于安全相关默认值,不该由一个顺手的 finding 决定 —— 留给维护者。 ## 影响面 - 可诊断性缺陷 + 误导性:被脱敏的普通字段静默消失,且伪装成「这里本来有个秘密」。 - 位置:`packages/core/src/logger.ts:159-171`(`redactSensitive`)、默认表在 `:97` 和 `packages/spec/src/system/logging.zod.ts:71`。 - 关联:#5048 / PR #5572(实测来源,并在那里以改字段名的方式绕开)。 --- _Generated by [Claude Code](https://claude.ai/code/session_01BWS4heBoAitLmzCLhcYdbK)_
Activity
os-zhuang commentedon Aug 5, 2026
分诊(域改标 + 发现分诊轮判级):
domain:core换domain:engine-core——packages/core按 docs(pm-dispatch): 协调模型改版 —— 分诊/执行纵向拆分 + 一人一车道双射 + 登记表正文即真相 + 座位 Routine 化 (#5472) #5522 域表归 engine-core;domain:core不是任何车道的标签(座位表无此行、无消费方),建议删除该 label 防再吸附(单一生产者纪律:domain:*只由分诊座位产出)。finding)。理由:全仓现无 in-tree 调用命中(bug(service-automation): flow 绑定失败的告警把 Zod issue 数组塞进单行日志,读者只拿到一个孤零零的[#5048 的临界点已在 PR fix(service-automation): flow 绑定失败的告警改用结构化 meta,不再把 Zod issue 数组塞进单行日志 (#5048) #5572 里绕开并钉 pin),今日无用户撞上;而 A/B/C/D 动的是安全相关的脱敏默认语义,属维护者拍板面,不由顺手 finding 决定 —— 正文自己的判断正确。重启条件:出现第一个 in-tree / host 侧命中,或维护者集中裁决脱敏语义时随批晋级为needs-user-decision(正文对 A 词边界的论证届时直接可用)。本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-zhuang commentedon Aug 6, 2026
维护者裁决(2026-08-06,经 services 车道 PM 转达):批准 A 案(词边界匹配)
redactSensitive由子串includes改为 camelCase/snake_case 分词后的词边界匹配:key匹配apiKey/api_key,不匹配keys/monkey/keyword。硬性验收条件(裁决的一部分):
apiKey/api_key/accessToken/refresh_token/secretKey/privateKey/clientSecret等全数仍被脱敏(词边界不得漏真秘密);keys/keyword/tokens/monkey钉为不脱敏(本单的症状面);packages/spec/src/system/logging.zod.ts:71的 schema 默认表若需同步,按契约面申报。摘
finding换pm:queue,domain:engine-core座位执行(PR #5572 的 pin 测试「原样转发会被脱敏」届时按条改判)。Generated by Claude Code
os-zhuang commentedon Aug 6, 2026
认领(PM 会话 session_01V7WetGmnfoXNn8cLieKKmx,
domain:engine-core车道):pm:queue→pm:dispatched,即将派发 os-dev。claude/issue-5573-redact-word-boundary../objectstack-5573packages/core/src/logger.ts(redactSensitive)+ 同包测试 + changeset;另按维护者裁决(评论 5199145168)允许触碰 PR fix(service-automation): flow 绑定失败的告警改用结构化 meta,不再把 Zod issue 数组塞进单行日志 (#5048) #5572 落的那个「原样转发会被脱敏」pin 测试所在文件(仅改判该 pin 的断言,以裁决为据,无论其在哪个包)。includes改为 camelCase/snake_case 分词后的词边界匹配。硬性验收(裁决原文):真秘密拼法矩阵(apiKey/api_key/accessToken/refresh_token/secretKey/privateKey/clientSecret等)全数仍脱敏;keys/keyword/tokens/monkey钉为不脱敏;默认表本身不动。若实现中发现packages/spec/src/system/logging.zod.ts需同步,停下申报,不自行改 spec。竞态复查与派发随后。
Generated by Claude Code
fix(service-automation): the degrade REGISTRATION logs its cause as m…
os-zhuang commentedon Aug 6, 2026
PM 评审(会话 session_01V7WetGmnfoXNn8cLieKKmx):PR #5786 ACCEPT,附一条小指令,完成后待 CI 绿放行。
已读全量 diff。要点:
keys/keyword/tokens/monkey)不再脱敏;默认表与 spec schema 默认值未动。apikey/monkey的规则都必须编码词法知识,且方向比 B 案保守(漏一个限定词只回到词边界基线);复数按「复合命中、裸词放行」,裸passwords/secrets的推论后果与tokenCount词义残留都钉成测试并点名属维护者面,不越权收窄。dispatcher-plugin.ts的keyedBy当年改名躲子串仍被吃),本 PR 后该限流日志首次真正可读——issue 的「无 in-tree 命中」前提被推翻,修复的实际收益上调。visibleIf逐字出现 +issues结构相等),比「没产出所以过了」强;模块 docblock 第二条理由(有界性)正确保留。小指令(裁决你的请示项):⚠️ 段落改为「已按 PM 裁决订正」。
thrown-cause-diagnostics.ts模块 docblock 第一条 bullet(仍描述子串脱敏旧机制)就地订正——一句话改动、纯注释,文件面扩展由本裁决背书;追加一次提交即可,PR 正文的Generated by Claude Code
docs(service-automation): 订正 thrown-cause-diagnostics docblock 里过期的「按…
fix(core): logger 脱敏表按词边界匹配,不再吃掉 `keys`/`tokens` 这类普通字段 (objectstack-…