Repository navigation
fix(core): redact error text in usage-statistics telemetry sink - #11649
Conversation
887b430 to
7df466e
Compare
|
Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration. 中文请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。 |
Close the highest-severity review findings on the redaction table: - Add `error` to ERROR_TEXT_PROPERTY_KEYS so hook failure text written to properties['error'] is redacted at the enqueue choke point (R1-1). - Skip any auth scheme (Basic/token/Digest/…) before the value, not only Bearer, so `Authorization: Basic <creds>` no longer leaks (R1-2). - Add `key` to the env key alternation so *_API_KEY / *_ACCESS_KEY* are redacted (R1-6). - Run redaction on the raw text before stripAnsiAndControl so the whitespace-delimited patterns still see their newline/tab delimiters (R1-7). - Share one secret-value pattern that accepts a quote-opened value (R1-3) and let the flag name contain the secret keyword anywhere (R1-4). Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtwznj86xz
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtx1sp0ry2
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-conflict/jmtx4nkoby7
|
@qwen-code /triage |
Harden the usage-statistics error-text redaction pass against the review findings on this branch: - Normalise shell line continuations (`\` + newline + leading whitespace) ahead of every pass, so a credential split across a continuation (including a URL credential) can't survive as a glued cleartext run. - Make SECRET_VALUE quote-aware (a quoted run matches to its closing quote), so interior quotes and quoted values containing spaces redact whole instead of leaking a suffix after the marker. - Give the env pattern the same quoted alternative behind its floor. - Skip an optional leading backslash in each separator so a shell-escaped nested quote (`Authorization: Bearer \"secret\"`) still redacts. - Strip ANSI/C0 per line (after widening tabs to spaces) before the patterns run, so an escape or control character can't hide or reassemble a credential the pattern pass never saw. Fix the line-continuation test to assert the secret is absent rather than merely that no marker is emitted, and add regression cases for interior quotes, quoted values with spaces, ANSI/C0, and continuations. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtxennouyn
- Bound the non-secret name chars around the secret keyword in
SECRET_FLAG_PATTERN and ENV_SECRET_PATTERN (previously unbounded `*`,
now `{0,64}`) to prevent quadratic backtracking on adversarial error
text that runs through the synchronous telemetry redaction pass.
- Give SECRET_FLAG_PATTERN the same 10-char value floor as
ENV_SECRET_PATTERN so `--max-tokens 8192` and `max_tokens=8192` are
handled consistently (both pass through untouched).
- Align the ENV_SECRET_PATTERN docblock examples with the actual 10-char
floor (the old `ghs_xxx` / `sk_xxx` examples were shorter than the floor
and were never redacted).
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtxgstgfyr
|
@qwen-code /triage |
|
Closeout at R5-1 is stale: both language versions of the PR description now say Four threads remain for explicit disposition:
Recommendation: explicitly accept or reject whole-field masking first. I favor keeping raw error text out of this default-on channel, with structured diagnostics designed separately. No broader privacy guarantee is claimed for unknown keys, snapshots, stack, or other telemetry sinks. No additional review run is requested for this comment-only pass. |
`redactEventErrorText` already replaces every string under `error_message` / `error_excerpt` / top-level `message` with a fixed marker at the enqueue boundary, so `logHookCallEvent`'s `getTelemetryLogPromptsEnabled()` gate on `properties['error']` could only ever emit the constant `'***REDACTED***'` — a consent flag that no longer gates any content. Hook failure is already signalled by `success` / `exit_code`, so the raw error text is dropped fail-closed instead, and the now-dead `'error'` entry is removed from `ERROR_TEXT_PROPERTY_KEYS`. The failed-hook test now drives a config with telemetry log prompts enabled and asserts `properties` carries no `error` key, which goes red when the gated assignment is restored. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtycy85205
|
Removed the automatic-closing keyword from the PR and made the current whole-field replacement policy and pending maintainer decision explicit in its description. This corrects the stale completion claim; it does not decide the privacy-versus-diagnostics tradeoff. Leaving the policy thread open for that decision. 中文说明已移除 PR 的自动关闭关键字,并明确当前实现是整字段替换、仍需维护者决定隐私与诊断信息的取舍。此次仅纠正完成声明,不代替策略决策,因此保留本线程。 |
chiga0
left a comment
There was a problem hiding this comment.
Review Summary
Files reviewed
packages/core/src/telemetry/qwen-logger/qwen-logger.tspackages/core/src/telemetry/qwen-logger/qwen-logger.test.ts
Findings
No blockers found.
Clean areas
- Single choke point:
redactEventErrorTextis called at the top ofenqueueLogEventbefore the event enters the deque. All callers route throughenqueueLogEvent, so no error text can bypass redaction. - Fail-closed hook approach: Instead of redacting
properties['error'], the PR removes the hook error forwarding code entirely. Hook failures still signal viasuccess: 0/exit_code. Most conservative option — eliminates the leakage class rather than sanitizing it. - Redaction coverage:
ERROR_TEXT_PROPERTY_KEYS = ['error_message', 'error_excerpt']plus top-levelmessageon exception/resource events covers all error-text carriers. - Type safety:
(event as RumExceptionEvent).messagecast is TypeScript-only narrowing;typeof message === 'string'guard prevents mutation on events without the field. - Test correctness: Uses
toEqualfor exact property-shape matching, correctly verifies redaction while preservingerror_type. Hook test correctly assertsnot.toHaveProperty('error').
Needs human review
- Diagnostic trade-off: Hook error text is now completely absent from telemetry. Confirm that
success: 0+exit_codeprovides sufficient diagnostic signal. - Stale CHANGES_REQUESTED: The standing review references regex patterns that no longer exist at HEAD. Should be dismissed if the current approach is accepted, otherwise
mergeStateStatusremainsBLOCKED.
Reviewed with AI assistance.
qqqys
left a comment
There was a problem hiding this comment.
APPROVE
核对基线:head ea640b45c2fde6767ebbff99712dac8b48461583。
历史阻塞问题:已解除
本 PR 历史上有三次 CHANGES_REQUESTED,最后一次是 2026-09-12T02:54:50Z 针对 41714d9f;当前 head 之后最新的一次 review(2026-09-12T14:57:47Z,针对 ea640b45)已降级为 COMMENTED,不再要求变更。43 条线程里 4 条未解决(R4-1、R4-2、R4-3、R5-2),逐条读过,全部是 [Suggestion] 级,没有 Critical、安全、数据损坏或回归级别的历史阻塞问题。按本渠道策略 Suggestion 不作为合入门禁。
本轮独立扫描:未发现 Critical
这是一个隐私收口改动,我按「是否存在绕过 redaction 的出口」和「是否存在未被覆盖的错误文本载体」两条主线核对:
- redaction 确实是唯一收口点。 出站队列
this.events的写入只有两处:enqueueLogEvent内的this.events.push(event)(:195),以及重试回填的this.events.unshift(eventsToRequeue[i])(:1175)。前者的第一行就是this.redactEventErrorText(event)(:187);后者回填的是此前已经入过队、因而已经被改写过的事件,不会把原文重新带回来。发送侧this.events.toArray()(:304、:377)只从队列读取,没有旁路构造 payload 的路径。因此所有出站事件都经过 redaction。 - 错误文本的载体被覆盖完整。 该 sink 里携带错误文本的字段只有三个:
properties.error_message(:574、:703、:880-881、:908)、properties.error_excerpt,以及 exception 事件顶层的message(:696、:716)。ERROR_TEXT_PROPERTY_KEYS覆盖了前两个,redactEventErrorText的后半段单独处理了message(:218-221),且都做了typeof === 'string'判定,不会把非字符串值改坏。我另外按error|stack|stack_trace|details|excerpt|reason|message扫过整个文件的字段位,没有发现第四个承载原文的键。 - hook 路径的处理是收紧而非放松。 删掉的
if (event.error && this.config?.getTelemetryLogPromptsEnabled()) properties['error'] = event.error;原本会在开启 logPrompts 时把 hook 的原始错误文本放进properties.error——而这个键不在ERROR_TEXT_PROPERTY_KEYS里。直接删除该赋值后,这条路径不再产生任何错误原文,方向是更严格,不构成泄漏。 - 过度改写只发生在一处且是安全方向。
:729的message: \Content retry failed after ${event.total_attempts} attempts`是只含数字的构造串,也会被统一改写成REDACTED`。这是损失一点诊断信号,不是正确性问题,且失败方向是「少发数据」,可以接受。
CI:当前 head 上 Lint & Static、Test (ubuntu-latest, Node 22.x)、Integration Tests (no-AK, No Sandbox)、review-pr 全部 pass,没有由本 PR 引入的失败。
其余意见
未解决的 4 条 Suggestion 里有两条值得后续处理,但都不阻塞本次合入:R4-2 指出 redactEventErrorText 是就地改写传入的事件对象(生产路径上事件由各 create*Event 帮助函数在 enqueue 前逐次新建,因此不影响调用方状态,但测试若在 enqueue 后再观察同一对象就会看到被改写后的值,容易写出恒真断言);R4-3 指出本 head 采用的 redaction 策略与关联 issue 上记录的相反,而 Fixes #11198 会自动关闭该 issue,建议在 issue 或 PR 里补一句说明以免留下矛盾记录。
结论:历史阻塞问题已解除,本轮未发现可证明的 Critical,提交 APPROVE。
|
Posted as a PR comment rather than a review: this head already carries a pending review by the author (11 drafted replies), and GitHub rejects a second pending review. Nothing was deleted. Reviewed at Verdict: no blocking defect at this head. The approach is right and strictly narrower than Two notes that are real but non-blocking. Both are already carried by the standing bot threads (
Two things I checked and found clean, so they are not findings:
On the standing |
qwen-code-dev-bot
left a comment
There was a problem hiding this comment.
REQUEST_CHANGES
已核对 head ea640b45c2fde6767ebbff99712dac8b48461583(vs merge-base 20ecdaf6b2)。required CI 全绿。方向我是赞成的:把遮罩从生产者侧移到 sink 入口(enqueueLogEvent → redactEventErrorText,qwen-logger.ts:184-221)比逐个生产者各自记得脱敏可靠,error_message / error_excerpt / exception message 三类字符串字段被整体替换为固定标记,按构造就不会漏。拦的是下面两条,都落在本改动自己身上。
1. 这个改动自己的隐私保证没有用例钉住(线程 R5-2,成立)
qwen-logger.test.ts:782-824 这条改名成 “without forwarding raw error text” 的用例,唯一的值级断言是 expect(callArgs.properties).not.toHaveProperty('error'),其余全是 expect.objectContaining(容忍新增键),而它自己的 fixture 仍然带着 stderr: 'error output' 与 error: 'Command failed'(第 10、11 位实参)。所以同样的原文换到 hook_error / failure_reason 任何别的键下,这条用例照绿、原文照发到 RUM。上面的注释还写着「no raw error property is forwarded to the sink」,即用例在声明一个它并不检查的保证;而 sink 的防护本身是按键名匹配的黑名单,对新键名不设防,这一点恰是需要值级断言的理由。
同文件 :762-779 已经示范了该写的形状:把事件序列化后对原文取 not.toContain。请把这条补上(对 fixture 里的两个原文串断言 not.toContain),否则本 PR 的核心契约只有键名缺席在守。
2. 上一轮的修法顺手让 4 类 hook 里 3 类的失败再无归因(线程 R4-1,成立)
本轮删掉了 properties['error'] = event.error(连同 ERROR_TEXT_PROPERTY_KEYS 里的 'error'),留下的注释理由是「失败已由 success / exit_code 表明」。这个前提只对 hook_type: 'command' 成立:HookExecutionResult.exitCode 是可选的(hooks/types.ts:1340),全仓非测试代码里只有 hookRunner.ts:1439 会写它(http / prompt / function 三类都不写),logHookCallEvent 只有 6 个属性,序列化时 undefined 键被丢弃,于是 HTTP hook 的 URL 校验失败、被中止、超时、传输错误四类在用量遥测里落成逐字节相同的记录,hook_type 之外没有任何区分因。
请给一个不含自由文本的归因位(例如固定的错误类别枚举,或对非命令类失败也落一个 sentinel),并保持整字段遮罩不回退;如果决定本轮就不补,请把这一点写进 PR 正文的取舍段并在 #11198 上记一笔,别让它随合并静默消失。
两条已核为过期或不阻塞的说明
- 线程 R4-3 说本 head 的隐私策略与
#11198上记录的方案相反、且Fixes #11198会在合并时自动关闭该 issue。策略相反这点确实成立(issue 上作者 2026-09-11T10:07 那条还在说 shape-based masking「让错误保持可调试」,现已改为整字段替换),但 自动关闭的前提在本 head 已不成立:PR 正文用的是Refs #11198(:49、:102),并明确写了「this PR must not automatically close that issue before the decision is recorded」。不过该 issue 仍带status/ready-for-human、category/security、scope/data-privacy且核心遥测 owner 已两次把「遮罩策略由维护者定」升级上来 —— 请在合并前先由维护者在#11198记下裁决(整字段替换 vs 形状遮罩),别让这个 P1 隐私决策随合并默认生效。 - 线程 R4-2(
redactEventErrorText就地改写调用方对象、createRumEvent浅拷贝properties,QwenLogger又是指向公开的包 API)我复核为事实,但正如提出者所说,当前 45 个左右调用点都传新构造的字面量,所以今天不伤生产,只伤测试可观察性与易用性。建议顺手改成先浅拷贝再脱敏、入队那份拷贝,断言指向真正被序列化的对象(同文件:280有现成写法)。
Merge origin/main into codex/issue-11198-redact-rum-errors. Main landed QwenLM#11649, which replaces the RUM enqueue boundary's `message`, `error_message`, and `error_excerpt` with a fixed marker outright. This branch masks only credential-bearing shapes in those same fields plus `stack` and `properties.error`. Both sides edited the one choke point, so the calls collided. Keep both passes rather than picking a side: the blanket one runs first and owns the three fields it replaces, then the targeted one covers what it leaves raw. Neither side's coverage is lost, and the stricter base-branch policy is not weakened. `redactErrorText` is a no-op on the marker, so the order is not output-load-bearing and only avoids re-scanning text that is about to be discarded.
What this PR does
Usage-statistics telemetry forwards raw tool and provider error text to the RUM endpoint. This PR replaces the known free-form error fields (
error_message,error_excerpt, and top-levelmessage) with a fixed***REDACTED***marker at the single enqueue boundary before an event can leave the process. (The hookerrorproperty is removed at its producerlogHookCallEventrather than marker-replaced, sohook_call#*.properties.erroris no longer collected at all.)Why it's needed
A failed shell command can contain a credential in a URL, an authorization header, a flag, an environment assignment, truncated output, or an unanticipated form. Parsing that untrusted text with a growing regex denylist cannot prove that the credential is gone and can itself become a CPU denial-of-service path. Replacing the whole error string is smaller and fails closed.
Reviewer Test Plan
How to verify
Run the focused unit suite:
The regression drives the public enqueue boundary with all currently known error-text properties plus a top-level exception message. It asserts that every raw value is replaced while a non-text classification field is unchanged.
Evidence (Before & After)
Before: an event whose error text contained
git clone https://x-access-token:ghs_testsecret123@github.com/...was enqueued with the complete command and token.After: the entire error-text field is enqueued as
***REDACTED***; no input substring is retained or parsed.Tested on
Environment (optional)
Unit tests only —
npx vitest runinsidepackages/core.Risk & Scope
The implementation now replaces whole error fields, superseding the earlier shape-based masking proposal recorded on #11198. The privacy/diagnostics tradeoff still needs maintainer confirmation; this PR must not automatically close that issue before the decision is recorded.
snapshotsandstackare separate structured fields and are not changed here; any sensitive producer-to-sink path through them should be handled separately.Linked Issues
Refs #11198
中文说明
本 PR 做了什么
usage-statistics 遥测会把工具和 provider 的原始错误文本发送到 RUM endpoint。本 PR 在事件离开进程前的唯一入队边界,将当前已知的自由文本错误字段(
error_message、error_excerpt和顶层message)统一替换为固定的***REDACTED***标记。(hook 的error属性在其生产者logHookCallEvent处被移除、而非替换为标记,因此hook_call#*.properties.error不再被采集。)为什么需要它
失败的 shell 命令可能以 URL、Authorization header、命令参数、环境变量、截断输出或任意其他形式携带凭据。不断扩展正则黑名单无法证明凭据已被完整清除,还会为不可信错误文本引入 CPU 拒绝服务风险。整段替换更小,也能失败关闭。
Reviewer 测试计划
如何验证
运行聚焦的单元测试:
回归测试直接走公开的入队边界,覆盖当前所有已知错误文本属性和顶层异常消息;它断言原始值全部被替换,同时非文本分类字段保持不变。
证据(Before & After)
Before:错误文本包含
git clone https://x-access-token:ghs_testsecret123@github.com/...的事件,会带着完整命令和 token 入队。After:整段错误文本被替换为
***REDACTED***,不保留也不解析任何输入片段。测试环境
环境(可选)
仅单元测试——在
packages/core内运行npx vitest run。风险与范围
当前实现替换整个错误字段,已不同于 #11198 先前记录的按内容模式遮罩方案。隐私与诊断信息之间的取舍仍需维护者确认,因此本 PR 不应在决策记录之前自动关闭该 issue。
snapshots和stack是独立的结构化字段,本 PR 不修改;如果它们存在敏感的 producer-to-sink 路径,应单独处理。关联 Issue
Refs #11198