Files
nofx/api/utils.go
Lawrence Liu 90d09e63e5 fix(security): 脱敏后台日志中的敏感信息 (#761)
## 问题
后台日志在打印配置更新时会暴露完整的 API Key、Secret Key 和私钥等敏感信息(Issue #758)。
## 解决方案
### 1. 新增脱敏工具库 (api/utils.go)
- `MaskSensitiveString()`: 脱敏敏感字符串(保留前4位和后4位,中间用****替代)
- `SanitizeModelConfigForLog()`: 脱敏 AI 模型配置用于日志输出
- `SanitizeExchangeConfigForLog()`: 脱敏交易所配置用于日志输出
- `MaskEmail()`: 脱敏邮箱地址
### 2. 修复日志打印 (api/server.go)
- Line 1106: 脱敏 AI 模型配置更新日志
- Line 1203: 脱敏交易所配置更新日志
### 3. 完善单元测试 (api/utils_test.go)
- 4个测试函数,9个子测试,全部通过
- 工具函数测试覆盖率: 91%+
## 脱敏效果示例
**修复前**:
```
✓ 交易所配置已更新: map[binance:{api_key:sk-1234567890abcdef secret_key:binance_secret_1234567890abcdef}]
```
**修复后**:
```
✓ 交易所配置已更新: map[binance:{api_key:sk-1****cdef secret_key:bina****cdef}]
```
## 测试结果
```
PASS
ok  	nofx/api	0.012s
coverage: 91.2% of statements in utils.go
```
## 安全影响
- 防止日志泄露 API Key、Secret Key、私钥等敏感信息
- 保护用户隐私和账户安全
- 符合安全最佳实践
Closes #758
2025-11-08 19:33:13 +08:00

98 lines
2.8 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 api
import "strings"
// MaskSensitiveString 脱敏敏感字符串只显示前4位和后4位
// 用于脱敏 API Key、Secret Key、Private Key 等敏感信息
func MaskSensitiveString(s string) string {
if s == "" {
return ""
}
length := len(s)
if length <= 8 {
return "****" // 字符串太短,全部隐藏
}
return s[:4] + "****" + s[length-4:]
}
// SanitizeModelConfigForLog 脱敏模型配置用于日志输出
func SanitizeModelConfigForLog(models map[string]struct {
Enabled bool `json:"enabled"`
APIKey string `json:"api_key"`
CustomAPIURL string `json:"custom_api_url"`
CustomModelName string `json:"custom_model_name"`
}) map[string]interface{} {
safe := make(map[string]interface{})
for modelID, cfg := range models {
safe[modelID] = map[string]interface{}{
"enabled": cfg.Enabled,
"api_key": MaskSensitiveString(cfg.APIKey),
"custom_api_url": cfg.CustomAPIURL,
"custom_model_name": cfg.CustomModelName,
}
}
return safe
}
// SanitizeExchangeConfigForLog 脱敏交易所配置用于日志输出
func SanitizeExchangeConfigForLog(exchanges map[string]struct {
Enabled bool `json:"enabled"`
APIKey string `json:"api_key"`
SecretKey string `json:"secret_key"`
Testnet bool `json:"testnet"`
HyperliquidWalletAddr string `json:"hyperliquid_wallet_addr"`
AsterUser string `json:"aster_user"`
AsterSigner string `json:"aster_signer"`
AsterPrivateKey string `json:"aster_private_key"`
}) map[string]interface{} {
safe := make(map[string]interface{})
for exchangeID, cfg := range exchanges {
safeExchange := map[string]interface{}{
"enabled": cfg.Enabled,
"testnet": cfg.Testnet,
}
// 只在有值时才添加脱敏后的敏感字段
if cfg.APIKey != "" {
safeExchange["api_key"] = MaskSensitiveString(cfg.APIKey)
}
if cfg.SecretKey != "" {
safeExchange["secret_key"] = MaskSensitiveString(cfg.SecretKey)
}
if cfg.AsterPrivateKey != "" {
safeExchange["aster_private_key"] = MaskSensitiveString(cfg.AsterPrivateKey)
}
// 非敏感字段直接添加
if cfg.HyperliquidWalletAddr != "" {
safeExchange["hyperliquid_wallet_addr"] = cfg.HyperliquidWalletAddr
}
if cfg.AsterUser != "" {
safeExchange["aster_user"] = cfg.AsterUser
}
if cfg.AsterSigner != "" {
safeExchange["aster_signer"] = cfg.AsterSigner
}
safe[exchangeID] = safeExchange
}
return safe
}
// MaskEmail 脱敏邮箱地址保留前2位和@后部分
func MaskEmail(email string) string {
if email == "" {
return ""
}
parts := strings.Split(email, "@")
if len(parts) != 2 {
return "****" // 格式不正确
}
username := parts[0]
domain := parts[1]
if len(username) <= 2 {
return "**@" + domain
}
return username[:2] + "****@" + domain
}