Files
HomeAgent/internal/agent/core/log_scrub.go
JianFeeeee 59f7001932 fix(security): 工具输出与参数会原样写进日志 —— 落盘前脱敏
## 缺陷

两处日志把工具内容原样打印,没有经过 security.Replace:

	task.go:1038  log.Printf("[agent] tool %s result: %s", ..., truncateStr(result, 100))
	task.go:967   log.Printf("[agent] tool %s raw_arguments: %s", ..., truncateStr(tc.RawArguments, 300))

实测场景:enc_shell 跑 curl 拿到 200,响应体里带着上游 key 的完整值
(`{"key":"sk-..."}`),这一行直接写进 /home/newqqagent/log/homed_*.log。

## 为什么日志泄漏比上下文泄漏更严重

	模型上下文  短期——一轮结束就不再发送
	日志        长期——每天一个文件、从不自动清理,systemd journal 另存一份,
	                   排查问题时还会被贴进 issue

一次落盘就是永久泄漏。凭据轮换前,任何拿到日志的人都能直接用。

## 改动

 · 新增 scrubForLog(text, where):唯一入口,空安全(Global()==nil 时原样返回)
 · 两处日志调用点都改为先 scrubForLog 再落盘
 · 拦截本身记一笔(只记模式与长度,不记原文),便于事后发现"哪个工具常吐凭据"

## 判据(4 条)

 · 明文被换成别名(真 Store,不用 mock)
 · 普通文本不被改动(不误伤)
 · Global() 为 nil 时不 panic(安全模块未启用是常态)
 · **接线**:两处调用点确实走了 scrubForLog(只测函数不测接线会漏掉原缺陷)

变异:scrubForLog 直接返回原文 → 咬;工具结果日志去掉 scrubForLog → 咬。
2026-10-18 13:20:54 +08:00

61 lines
2.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package core
// 日志脱敏:写进日志的文本必须先清洗明文凭据。
//
// ## 为什么这条比"拦住模型"更紧迫
//
// 模型上下文是**短期**的:一轮对话结束就不再发送。
// 而日志是**长期**的:
//
// /home/newqqagent/log/homed_*.log 每天一个文件,从不自动清理
// systemd journal 另存一份
// 用户/技术支持/issue 排查时会把日志贴出来
//
// 一次泄漏进日志 = 永久泄漏。凭据被轮换前,任何拿到日志的人都能用。
//
// ## 具体泄漏点(2026-10-18 审计)
//
// task.go:1031 log.Printf("[agent] tool %s result: %s", ..., truncateStr(result, 100))
// ← 工具输出可能含服务端回显的凭据(enc_shell 跑 curl 拿 200)
// task.go:967 log.Printf("[agent] tool %s raw_arguments: %s", ..., truncateStr(tc.RawArguments, 300))
// ← 模型给的原始参数。正常是别名,但插件工具可能被直接传明文
//
// 两处都**原样打印**,没有经过 security.Replace。
//
// ## 实现
//
// scrubForLog 是唯一入口,所有"把工具内容写进日志"的地方都走它。
// 空安全:security.Global() 未初始化时原样返回(不 panic、不阻塞)。
// 幂等:已是别名(sec_xxx)的文本再扫一次不会被改动。
import (
"log"
"github.com/JianFeeeee/HomeAgent/internal/security"
)
// scrubForLog 清洗要写进日志的文本。
//
// where 用于日志里标注拦截位置(如 "tool_result:enc_shell"),
// 便于事后审计"哪些工具的输出带凭据"。
func scrubForLog(text, where string) string {
if text == "" {
return text
}
st := security.Global()
if st == nil {
return text
}
out, evts := st.Replace(text, "log:"+where, security.Patterns)
if len(evts) == 0 {
return text
}
// 拦截本身要记一笔(不打印原文,只打印模式与长度)——
// 否则运维不知道"这个工具经常吐凭据",也就不会去修插件。
for _, e := range evts {
log.Printf("[security] 日志拦截 %s ← %s(%d 字节),原文未落盘",
where, e.Kind, e.Len)
}
return out
}