Files
Cyrene_v2/plugins/tools/io/http_blocked_log_test.go
T
AskaEth 0f9251b983 fix(channel): 渠道适配器与出站工具的上游响应体/带凭据 URL 不再进日志与错误(M41)
接 M38 的 18 条真泄漏里「渠道适配器 + 出站工具」这一族(#10 #11 #12 #13 #15 #16)。
只动日志与 error 文字:发送/接收、解析口径、重试次数一个都没改。

- httpclient.do:非 2xx 不再把 512B 响应体原文拼进 error,改 trace.UpstreamError
  (状态/体积/sha256 前缀)+ 信封 code/message(RedactText + 200 rune);error 里的
  target 改 trace.HostOf,并剥掉 *url.Error 外壳(它带整条地址)
- telegram doJSON:4xx 只解 error_code/description(清洗 + 截断),体其余字段不拼;
  传输错误只留来源 —— Bot API 把 token 写在地址路径里
- weixin_ilink doJSON:4096B 体 ⇒ trace.UpstreamError,rawURL ⇒ mediafetch.HostOf;
  uploadOnce 剥掉预签名上传地址(encrypted_query_param),重试日志补来源主机
- qq_obv11 connectClient 成功 INFO:改走同包已有的 maskAccessTokenQuery
  (OneBot 惯例把 ?access_token= 写进 remote_url)
- plugins/tools/io http 拦截日志:改记 trace.HostOf + 原因码,不记原 URL、也不记错误
  原文(解析失败时它包着 *url.Error)

每条都有能变异打红的用例:QQ/iLink 预签名地址的哨兵、三处 4xx 体哨兵、
user:pass@ 与 query 凭据;八处变异逐条验证变红。
2026-10-26 13:47:56 +08:00

72 lines
2.6 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 io
// 本文件钉住 http 工具的**拦截日志**(M41 #16):rawURL 由模型/用户给,可能带
// `user:pass@` 或 `?api_key=`,而拦截日志是审计面、会被采集与转贴。旧实现把 rawURL
// 原样记进 WARN,等于把凭据写进日志。
//
// 现在只记来源主机与原因码;错误原文也不进日志 —— 解析失败时它包着 *url.Error,
// 里面就是整条原串。
import (
"bytes"
"context"
"errors"
"strings"
"testing"
"git.yeij.top/AskaEth/Cyrene_v2/internal/logger"
)
// blockedLog 返回一个把日志写进 buffer 的 http 工具。
func blockedLog(t *testing.T) (*httpTool, *bytes.Buffer) {
t.Helper()
buf := &bytes.Buffer{}
return httpToolForTest(t, WithLogger(logger.New(logger.Options{Out: buf, Level: "debug"}))), buf
}
// TestHTTPTool_BlockedLogHidesURLCredentials SSRF 拦截时日志只留来源与原因码。
func TestHTTPTool_BlockedLogHidesURLCredentials(t *testing.T) {
t.Parallel()
ht, logs := blockedLog(t)
_, err := ht.Execute(context.Background(), map[string]any{"url": "https://user:pass@127.0.0.1/x"})
if !errors.Is(err, ErrSSRF) {
t.Fatalf("err = %v, want ErrSSRF", err)
}
logged := logs.String()
if !strings.Contains(logged, "io http: blocked request") {
t.Fatalf("没有拦截日志,测试没覆盖到目标路径:\n%s", logged)
}
if strings.Contains(logged, "user:pass") || strings.Contains(logged, "pass@") {
t.Fatalf("user:pass@ 进了拦截日志:\n%s", logged)
}
if !strings.Contains(logged, "reason=ssrf_blocked") {
t.Fatalf("拦截日志缺原因码:\n%s", logged)
}
if !strings.Contains(logged, "[redacted]@127.0.0.1") {
t.Fatalf("拦截日志缺来源主机(诊断面不能一起丢):\n%s", logged)
}
}
// TestHTTPTool_BlockedLogHidesQueryCredentials 非 SSRF 的拒绝(这里是不支持的 scheme)
// 同样不得带出 query 里的凭据。
func TestHTTPTool_BlockedLogHidesQueryCredentials(t *testing.T) {
t.Parallel()
ht, logs := blockedLog(t)
_, err := ht.Execute(context.Background(), map[string]any{"url": "ftp://user:pass@files.example.com/x?api_key=QUERY-SECRET"})
if err == nil {
t.Fatal("不支持的 scheme 却报成功")
}
logged := logs.String()
if !strings.Contains(logged, "io http: blocked request") {
t.Fatalf("没有拦截日志,测试没覆盖到目标路径:\n%s", logged)
}
if strings.Contains(logged, "QUERY-SECRET") || strings.Contains(logged, "user:pass") {
t.Fatalf("拦截日志带出了原串里的凭据:\n%s", logged)
}
if !strings.Contains(logged, "reason=url_rejected") {
t.Fatalf("拦截日志缺原因码:\n%s", logged)
}
}