在做 #5048(PR #5572)时实测发现,顺手记录。
现象
packages/core/src/logger.ts 的 redactSensitive 递归遍历 meta,判定条件是子串包含:
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 决定 —— 留给维护者。
影响面
Generated by Claude Code
在做 #5048(PR #5572)时实测发现,顺手记录。
现象
packages/core/src/logger.ts的redactSensitive递归遍历 meta,判定条件是子串包含:默认脱敏表(
logger.ts:97,与packages/spec/src/system/logging.zod.ts:71的 schema 默认值一致)是['password', 'token', 'secret', 'key']。于是任何名字含这些子串的字段都会被整块替换,不问值是什么:keys(含key)keyword/keywords、keyboard、monkeytokenizer、tokens(计数!)secretary实测(
format: 'json',值是一个普通的字符串数组):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 就好」,而是需要维护者定一个方向,几个选项:key匹配apiKey/api_key(camelCase / snake_case 分词后是独立词),但不匹配keys/monkey。能同时保住覆盖面和精度,实现是分词而不是includes。keys、keyword、…)。便宜,但例外表永远追不齐,是治症状。keys: [...],但apiKeys: ['sk-…']就漏了 —— 方向不对。倾向 A(词边界),因为它在「不漏真秘密」和「不吃普通字段」之间不用二选一;但这动的是脱敏语义,属于安全相关默认值,不该由一个顺手的 finding 决定 —— 留给维护者。
影响面
packages/core/src/logger.ts:159-171(redactSensitive)、默认表在:97和packages/spec/src/system/logging.zod.ts:71。[#5048 / PR fix(service-automation): flow 绑定失败的告警改用结构化 meta,不再把 Zod issue 数组塞进单行日志 (#5048) #5572(实测来源,并在那里以改字段名的方式绕开)。Generated by Claude Code