接 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 凭据;八处变异逐条验证变红。
160 lines
6.1 KiB
Go
160 lines
6.1 KiB
Go
package weixin_ilink
|
||
|
||
// 本文件钉住 iLink 出站侧的两条出镜边界(M41 #10/#11 那一族):
|
||
//
|
||
// - **预签名上传地址不进日志与错误**:uploadURL 由 getuploadurl 签发,
|
||
// `encrypted_query_param` 就是下载凭据;而 net/http 的 *url.Error 会把整条地址
|
||
// 拼进错误串,那条错误要经 `cdn upload attempt failed, retrying` 与 send.go 的
|
||
// `outbound media item skipped` 两处落日志。
|
||
// - **非 2xx 的响应体原文不进错误**:iLink 在 errmsg 里回显请求内容,旧实现把 4096B
|
||
// 体原文拼进 error。
|
||
//
|
||
// 两条都是「改回去就变红」的形状:断言的是错误文本与日志文本本身。
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"net"
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"strings"
|
||
"testing"
|
||
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/logger"
|
||
)
|
||
|
||
// closedLoopbackAddr 返回一个**刚被关掉**的 127.0.0.1 端口地址。用它而不是拨一个
|
||
// 域名:这里要的是「连接被拒」这条确定的传输层失败 —— 只有传输层失败才会让
|
||
// net/http 包出带完整地址的 *url.Error,那正是要钉住的泄漏形态(不需要 DNS、不需要
|
||
// 外网、失败是立刻的)。
|
||
func closedLoopbackAddr(t *testing.T) string {
|
||
t.Helper()
|
||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||
if err != nil {
|
||
t.Fatalf("listen: %v", err)
|
||
}
|
||
addr := ln.Addr().String()
|
||
if cerr := ln.Close(); cerr != nil {
|
||
t.Fatalf("close listener: %v", cerr)
|
||
}
|
||
return addr
|
||
}
|
||
|
||
// newLeakTestAdapter 构造一个只用来发起出站调用的适配器:日志写进返回的 buffer,
|
||
// 便于断言「日志里搜不到哨兵」。
|
||
func newLeakTestAdapter(t *testing.T) (*Adapter, *bytes.Buffer) {
|
||
t.Helper()
|
||
buf := &bytes.Buffer{}
|
||
ad, err := New(
|
||
Config{BaseURL: "http://127.0.0.1:9/", BotToken: "tok"},
|
||
nil,
|
||
logger.New(logger.Options{Out: buf, Level: "debug"}),
|
||
)
|
||
if err != nil {
|
||
t.Fatalf("New: %v", err)
|
||
}
|
||
return ad, buf
|
||
}
|
||
|
||
// TestUploadCiphertext_PresignedURLStaysOutOfLogs 重试日志与最终错误里都不得出现
|
||
// 预签名地址的签名参数。
|
||
func TestUploadCiphertext_PresignedURLStaysOutOfLogs(t *testing.T) {
|
||
t.Parallel()
|
||
const secret = "PRESIGNED-ENCRYPTED-PARAM-SECRET"
|
||
ad, logs := newLeakTestAdapter(t)
|
||
|
||
uploadURL := "http://" + closedLoopbackAddr(t) + "/upload?encrypted_query_param=" + secret + "&filekey=abc"
|
||
_, err := ad.uploadCiphertext(context.Background(), uploadURL, []byte("cipher"))
|
||
if err == nil {
|
||
t.Fatal("连接被拒却报成功")
|
||
}
|
||
if strings.Contains(err.Error(), secret) {
|
||
t.Fatalf("预签名地址进了错误(会被 send.go 落日志):%v", err)
|
||
}
|
||
if !strings.Contains(err.Error(), "dial tcp") {
|
||
t.Fatalf("剥掉地址时把原因一起丢了:%v", err)
|
||
}
|
||
logged := logs.String()
|
||
if strings.Contains(logged, secret) {
|
||
t.Fatalf("预签名地址进了日志:\n%s", logged)
|
||
}
|
||
// 日志确实被写到了(否则上面的断言是空跑):重试行必须出现,且带来源主机。
|
||
if !strings.Contains(logged, "cdn upload attempt failed") {
|
||
t.Fatalf("没有重试日志,测试没覆盖到目标路径:\n%s", logged)
|
||
}
|
||
if !strings.Contains(logged, "host=http://127.0.0.1") {
|
||
t.Fatalf("重试日志缺少来源主机(诊断面不能一起丢):\n%s", logged)
|
||
}
|
||
}
|
||
|
||
// TestDoJSON_StatusErrorDoesNotEchoBodySentinel 非 2xx 的体原文不得进错误。
|
||
func TestDoJSON_StatusErrorDoesNotEchoBodySentinel(t *testing.T) {
|
||
t.Parallel()
|
||
const sentinel = "SENTINEL-ILINK-4XX"
|
||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
w.WriteHeader(http.StatusInternalServerError)
|
||
_, _ = w.Write([]byte(`{"ret":-1,"errcode":-14,"errmsg":"` + sentinel + `"}`))
|
||
}))
|
||
defer srv.Close()
|
||
|
||
ad, _ := New(Config{BaseURL: srv.URL, BotToken: "tok"}, srv.Client(),
|
||
logger.New(logger.Options{Out: &bytes.Buffer{}, Level: "debug"}))
|
||
var out map[string]any
|
||
err := ad.doJSON(context.Background(), http.MethodPost, ad.apiURL("/ilink/bot/getupdates"), nil, &out)
|
||
if err == nil {
|
||
t.Fatal("500 却报成功")
|
||
}
|
||
if strings.Contains(err.Error(), sentinel) {
|
||
t.Fatalf("响应体原文进了错误:%v", err)
|
||
}
|
||
if !strings.Contains(err.Error(), "status=500") || !strings.Contains(err.Error(), "body_bytes=") {
|
||
t.Fatalf("状态码/体积丢了(诊断面不能一起丢):%v", err)
|
||
}
|
||
if !strings.Contains(err.Error(), "127.0.0.1") {
|
||
t.Fatalf("错误里没有来源主机:%v", err)
|
||
}
|
||
}
|
||
|
||
// TestDoJSON_TransportErrorOmitsRequestURL 传输层失败时,错误里不得出现请求地址。
|
||
func TestDoJSON_TransportErrorOmitsRequestURL(t *testing.T) {
|
||
t.Parallel()
|
||
const secret = "QUERY-ENCRYPTED-PARAM-SECRET"
|
||
ad, _ := newLeakTestAdapter(t)
|
||
|
||
rawURL := "http://" + closedLoopbackAddr(t) + "/ilink/bot/getupdates?encrypted_query_param=" + secret
|
||
err := ad.doJSON(context.Background(), http.MethodPost, rawURL, nil, nil)
|
||
if err == nil {
|
||
t.Fatal("连接被拒却报成功")
|
||
}
|
||
if strings.Contains(err.Error(), secret) {
|
||
t.Fatalf("请求地址的签名参数进了错误:%v", err)
|
||
}
|
||
if !strings.Contains(err.Error(), "dial tcp") {
|
||
t.Fatalf("剥掉地址时把原因一起丢了:%v", err)
|
||
}
|
||
}
|
||
|
||
// TestUploadCiphertext_LogLevelIsWarn 钉住级别:重试行是 WARN(默认 INFO 级别下可见),
|
||
// 不是只有 debug 才看得到的取证行 —— 这条线要在生产默认级别下就安全。
|
||
func TestUploadCiphertext_RetryLogIsWarnAndCarriesHost(t *testing.T) {
|
||
t.Parallel()
|
||
const secret = "PRESIGNED-SECRET-2"
|
||
buf := &bytes.Buffer{}
|
||
ad, err := New(Config{BaseURL: "http://127.0.0.1:9/", BotToken: "tok"}, nil,
|
||
logger.New(logger.Options{Out: buf, Level: "info"}))
|
||
if err != nil {
|
||
t.Fatalf("New: %v", err)
|
||
}
|
||
uploadURL := "http://" + closedLoopbackAddr(t) + "/upload?encrypted_query_param=" + secret
|
||
if _, uerr := ad.uploadCiphertext(context.Background(), uploadURL, []byte("x")); uerr == nil {
|
||
t.Fatal("连接被拒却报成功")
|
||
}
|
||
logged := buf.String()
|
||
if !strings.Contains(logged, "level=WARN") {
|
||
t.Fatalf("重试行不是 WARN 级(默认 INFO 下会看不见):\n%s", logged)
|
||
}
|
||
if strings.Contains(logged, secret) {
|
||
t.Fatalf("预签名地址进了日志:\n%s", logged)
|
||
}
|
||
}
|