feat(harness): 输出脱敏增强 —— 跨分片 StreamRedactor + PII,杜绝密钥碎片泄漏
原逐片脱敏有两个漏:①密钥被切成两片("sk-912cf85b"|"16d0...")逐片都不命中正则而漏检; ②贪婪正则在缓冲末尾凑够最短长度就把半截密钥提前脱敏发走、剩余字符随后明文流出(碎片泄漏)。 有状态 StreamRedactor 跨分片缓冲,切点在「原文」上定且绝不切断任何完整匹配: - opener 暂留末尾仍在增长的疑似密钥(sk/AKIA/JWT/Bearer/手机/邮箱/长数字) - 始终留 16B 尾窗兜底 opener 未覆盖的短模式;勿切断完整匹配(循环至稳定) - rune 边界安全:cut 退到最近 rune 起点,中文不被切成半个发出乱码 - 暂留封顶 256B,防对抗性长串无限暂留 / O(n²) - 新增 PII:手机号 / 邮箱 / 身份证(18 位) 3 个流式点(graph/react_agent/compose_graph)统一接入,逐片 Push + 收尾 Flush。 7 单测(跨片/逐字符 JWT/碎片回归/尾窗内匹配/干净重建/PII/无误伤),-race 干净。 live 实测:26 位密钥(曾泄漏 ijkl90mnop 碎片)与邮箱整条 [已脱敏]。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -275,19 +275,19 @@ func (o *Orchestrator) runAgent(ctx context.Context, taskID string, b *board, sy
|
||||
msgs, _ := buildMessages(ctx, rc)
|
||||
tr.emit(node, "model", "start", "模型流式推理", "", 0)
|
||||
t0 := time.Now()
|
||||
n, redacted := 0, 0
|
||||
n := 0
|
||||
var produced strings.Builder // 本节点自身产出(用于沿图向下游传递)
|
||||
send := func(s string) {
|
||||
if s == "" {
|
||||
// 输出护栏:有状态流式脱敏,跨分片缓冲,杜绝密钥被切断而漏检(逐片正则会漏跨片密钥)。
|
||||
red := harness.NewStreamRedactor()
|
||||
emit := func(safe string) {
|
||||
if safe == "" {
|
||||
return
|
||||
}
|
||||
// 输出护栏:发射前逐片脱敏疑似密钥/令牌(流式无法回收已发,故逐片处理)。
|
||||
safe, hit := harness.RedactSecrets(s)
|
||||
redacted += hit
|
||||
_ = o.sink.PublishToken(taskID, []byte(safe))
|
||||
produced.WriteString(safe)
|
||||
n++
|
||||
}
|
||||
send := func(s string) { emit(red.Push(s)) }
|
||||
var err error
|
||||
if o.pool.Ready() {
|
||||
err = o.pool.ChatStream(ctx, toChatMessages(msgs), send)
|
||||
@@ -303,8 +303,9 @@ func (o *Orchestrator) runAgent(ctx context.Context, taskID string, b *board, sy
|
||||
}
|
||||
return
|
||||
}
|
||||
if redacted > 0 {
|
||||
tr.info(node, "system", "输出护栏", fmt.Sprintf("已脱敏 %d 处疑似密钥/令牌", redacted))
|
||||
emit(red.Flush()) // 吐出暂留的尾部(最后一段疑似密钥的判定)
|
||||
if red.Hits() > 0 {
|
||||
tr.info(node, "system", "输出护栏", fmt.Sprintf("已脱敏 %d 处疑似密钥/PII", red.Hits()))
|
||||
}
|
||||
o.recordAgentOutput(b, produced.String()) // 产出入黑板:成当前成稿 + 供下游接力
|
||||
tr.emit(node, "model", "end", "模型流式推理",
|
||||
|
||||
Reference in New Issue
Block a user