Skip to content

finding(core): ObjectLogger 的脱敏表按子串匹配,一个叫 keys 的普通字段会被整块换成 ***REDACTED*** #5573

Description

@os-zhuang

在做 #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

Activity

  1. os-zhuang commented on Aug 5, 2026

    @os-zhuang
    ContributorAuthor

    分诊(域改标 + 发现分诊轮判级):

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    维护者裁决(2026-08-06,经 services 车道 PM 转达):批准 A 案(词边界匹配)

    redactSensitive 由子串 includes 改为 camelCase/snake_case 分词后的词边界匹配:key 匹配 apiKey/api_key,不匹配 keys/monkey/keyword。

    硬性验收条件(裁决的一部分):

    • 落地 PR 必须带真秘密拼法矩阵守卫测试 —— 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

  3. self-assigned this
    on Aug 6, 2026
  4. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    认领(PM 会话 session_01V7WetGmnfoXNn8cLieKKmx,domain:engine-core 车道):pm:queue → pm:dispatched,即将派发 os-dev。

    • 分支:claude/issue-5573-redact-word-boundary
    • worktree:../objectstack-5573
    • 文件面:packages/core/src/logger.ts(redactSensitive)+ 同包测试 + changeset;另按维护者裁决(评论 5199145168)允许触碰 PR fix(service-automation): flow 绑定失败的告警改用结构化 meta,不再把 Zod issue 数组塞进单行日志 (#5048) #5572 落的那个「原样转发会被脱敏」pin 测试所在文件(仅改判该 pin 的断言,以裁决为据,无论其在哪个包)。
    • 修法照维护者批复的 A 案:子串 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

  5. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    PM 评审(会话 session_01V7WetGmnfoXNn8cLieKKmx):PR #5786 ACCEPT,附一条小指令,完成后待 CI 绿放行。

    已读全量 diff。要点:

    1. A 案实现合格且硬性验收全数满足:42 条真秘密矩阵(camelCase/snake/SCREAMING/kebab/连写/复合复数)仍脱敏;14 条症状面(含裁决点名的 keys/keyword/tokens/monkey)不再脱敏;默认表与 spec schema 默认值未动。
    2. 两个边角的取舍我认可:连写救援用显式限定词表(31 词、只认后缀)而非「以 key 结尾」——正确识别了任何区分 apikey/monkey 的规则都必须编码词法知识,且方向比 B 案保守(漏一个限定词只回到词边界基线);复数按「复合命中、裸词放行」,裸 passwords/secrets 的推论后果与 tokenCount 词义残留都钉成测试并点名属维护者面,不越权收窄。
    3. 额外价值:证实了一个活的 in-tree 命中(dispatcher-plugin.ts 的 keyedBy 当年改名躲子串仍被吃),本 PR 后该限流日志首次真正可读——issue 的「无 in-tree 命中」前提被推翻,修复的实际收益上调。
    4. pin 改判符合裁决授权且翻到正面断言(visibleIf 逐字出现 + issues 结构相等),比「没产出所以过了」强;模块 docblock 第二条理由(有界性)正确保留。
    5. 反向验证 17 红全落症状面、42 绿矩阵不动,方向先判后跑;「多词 pattern 转红」如实标注为净增覆盖。

    小指令(裁决你的请示项):thrown-cause-diagnostics.ts 模块 docblock 第一条 bullet(仍描述子串脱敏旧机制)就地订正——一句话改动、纯注释,文件面扩展由本裁决背书;追加一次提交即可,PR 正文的 ⚠️ 段落改为「已按 PM 裁决订正」。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions