Files
MyGoNavi/internal/ssh/ssh_network_name_test.go
Syngnat 1a8effb51f 🐛 fix(export,ssh,secret,explain): 修复 SQL 导出注入与六处静默数据丢失/资源泄漏
- SQL 导出:formatSQLValue 按方言转义反斜杠,修复 MySQL 系 dump 还原时静默改写数据、
  且以反斜杠结尾的值吞掉闭合引号致源库恶意行可执行任意 SQL 的问题;非 MySQL 方言保持原样
- 明文凭据:daily_secrets.json 由 0o644 改为 0o600、目录改 0o700,并对历史文件显式 Chmod
- SSH 隧道:RegisterSSHNetwork 改为确定性 network 名并复用缓存客户端,消除驱动全局 dialer
  表随重连线性增长、永久钉住 ssh.Client 的连接与 goroutine 泄漏
- xlsx 导入:单元格 r 属性列号增加 OOXML 16384 上限并提前熔断,避免篡改文件放大分配致 OOM
- 数据导出:三处非 SQL 导出入口捕获 file.Close 错误,不再在落盘失败时返回“导出成功”
- 数据根迁移:copyFile 捕获 Close 错误并补 Sync,避免被截断的副本被判定为迁移成功
- 执行计划:节点 ID 改为按 ExplainResult 派生,移除进程级全局计数器与 reset,
  修复并发诊断互相踩踏编号导致的重复 node ID 与错挂父子边
- 补充 5 个回归测试文件,含“导出→切分”闭环断言与未转义时的反向对照
2026-07-26 19:40:33 +08:00

62 lines
2.7 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 ssh
import (
"strings"
"testing"
"GoNavi-Wails/internal/connection"
)
// TestSSHNetworkNameIsDeterministicPerTarget 覆盖 go-sql-driver 自定义 network 名的确定性。
//
// 回归背景netName 原先是 fmt.Sprintf("ssh_%s_%d", host, time.Now().UnixNano())。
// mysql.RegisterDialContext 写入驱动内一张永不回收的全局 mapDeregisterDialContext 在本
// 仓库无任何调用点),因此每次(重)连接都会新增一条永久条目并钉住其闭包捕获的 ssh.Client
// 形成随重连线性增长的 SSH 连接与 goroutine 泄漏。改为按目标确定性派生后map 大小收敛为
// SSH 目标个数。
func TestSSHNetworkNameIsDeterministicPerTarget(t *testing.T) {
config := connection.SSHConfig{Host: "jump.example.com", Port: 22, User: "ops", Password: "s3cret"}
key := newSSHClientCacheKey(config)
first := sshNetworkName(key)
second := sshNetworkName(newSSHClientCacheKey(config))
if first != second {
t.Fatalf("同一目标两次派生出不同 network 名:%s vs %s会导致驱动全局 map 无界增长)", first, second)
}
if !strings.HasPrefix(first, "ssh_") {
t.Errorf("network 名 %q 缺少 ssh_ 前缀", first)
}
}
// TestSSHNetworkNameDistinguishesTargets 不同目标必须得到不同 network 名,
// 否则两条连接会共用同一个 dialer把流量打到错误的跳板机。
func TestSSHNetworkNameDistinguishesTargets(t *testing.T) {
base := connection.SSHConfig{Host: "jump.example.com", Port: 22, User: "ops", Password: "s3cret"}
variants := map[string]connection.SSHConfig{
"不同 host": {Host: "other.example.com", Port: 22, User: "ops", Password: "s3cret"},
"不同 port": {Host: "jump.example.com", Port: 2222, User: "ops", Password: "s3cret"},
"不同 user": {Host: "jump.example.com", Port: 22, User: "admin", Password: "s3cret"},
"不同 password": {Host: "jump.example.com", Port: 22, User: "ops", Password: "other"},
}
baseName := sshNetworkName(newSSHClientCacheKey(base))
for label, cfg := range variants {
if got := sshNetworkName(newSSHClientCacheKey(cfg)); got == baseName {
t.Errorf("%s 却派生出与基准相同的 network 名 %s", label, got)
}
}
}
// TestSSHNetworkNameDoesNotLeakSecrets network 名会进入 DSN 与日志,
// 不能包含主机名、用户名或口令明文。
func TestSSHNetworkNameDoesNotLeakSecrets(t *testing.T) {
config := connection.SSHConfig{Host: "jump.example.com", Port: 22, User: "ops", Password: "s3cretP@ss"}
name := sshNetworkName(newSSHClientCacheKey(config))
for _, secret := range []string{"jump.example.com", "ops", "s3cretP@ss"} {
if strings.Contains(name, secret) {
t.Errorf("network 名 %q 泄露了明文 %q", name, secret)
}
}
}