Files
lijiaoqiao/supply-api/internal/audit/sanitizer/sanitizer_test.go
Your Name b2d32be14f fix(P2): 修复4个P2轻微问题
P2-01: 通配符scope安全风险 (scope_auth.go)
- 添加hasWildcardScope()函数检测通配符scope
- 添加logWildcardScopeAccess()函数记录审计日志
- 在RequireScope/RequireAllScopes/RequireAnyScope中间件中调用审计日志

P2-02: isSamePayload比较字段不完整 (audit_service.go)
- 添加ActionDetail字段比较
- 添加ResultMessage字段比较
- 添加Extensions字段比较
- 添加compareExtensions()辅助函数

P2-03: regexp.MustCompile可能panic (sanitizer.go)
- 添加compileRegex()安全编译函数替代MustCompile
- 处理编译错误,避免panic

P2-04: StrategyRoundRobin未实现 (router.go)
- 添加selectByRoundRobin()方法
- 添加roundRobinCounter原子计数器
- 使用atomic.AddUint64实现线程安全的轮询

P2-05: 错误信息泄露内部细节 - 已在MED-09中处理,跳过
2026-04-03 09:39:32 +08:00

332 lines
8.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 sanitizer
import (
"regexp"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSanitizer_Scan_CredentialExposure(t *testing.T) {
// 检测响应体中的凭证泄露
scanner := NewCredentialScanner()
testCases := []struct {
name string
content string
expectFound bool
expectedTypes []string
}{
{
name: "OpenAI API Key",
content: "Your API key is sk-1234567890abcdefghijklmnopqrstuvwxyz",
expectFound: true,
expectedTypes: []string{"openai_key"},
},
{
name: "AWS Access Key",
content: "access_key_id: AKIAIOSFODNN7EXAMPLE",
expectFound: true,
expectedTypes: []string{"aws_access_key"},
},
{
name: "Client Secret",
content: "client_secret: c3VwZXJzZWNyZXRrZXlzZWNyZXRrZXk=",
expectFound: true,
expectedTypes: []string{"secret"},
},
{
name: "Generic API Key",
content: "api_key: key-1234567890abcdefghij",
expectFound: true,
expectedTypes: []string{"api_key"},
},
{
name: "Password Field",
content: "password: mysecretpassword123",
expectFound: true,
expectedTypes: []string{"password"},
},
{
name: "Token Field",
content: "token: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
expectFound: true,
expectedTypes: []string{"bearer_token"},
},
{
name: "Normal Text",
content: "This is normal text without credentials",
expectFound: false,
expectedTypes: nil,
},
{
name: "Already Masked",
content: "api_key: sk-****-****",
expectFound: false,
expectedTypes: nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := scanner.Scan(tc.content)
if tc.expectFound {
assert.True(t, result.HasViolation(), "Expected violation for: %s", tc.name)
assert.NotEmpty(t, result.Violations, "Expected violations for: %s", tc.name)
var foundTypes []string
for _, v := range result.Violations {
foundTypes = append(foundTypes, v.Type)
}
for _, expectedType := range tc.expectedTypes {
assert.Contains(t, foundTypes, expectedType, "Expected type %s in violations for: %s", expectedType, tc.name)
}
} else {
assert.False(t, result.HasViolation(), "Expected no violation for: %s", tc.name)
}
})
}
}
func TestSanitizer_Scan_Masking(t *testing.T) {
// 脱敏:'sk-xxxx' 格式
sanitizer := NewSanitizer()
testCases := []struct {
name string
input string
expectedOutput string
expectMasked bool
}{
{
name: "OpenAI Key",
input: "sk-1234567890abcdefghijklmnopqrstuvwxyz",
expectedOutput: "sk-xxxxxx****xxxx",
expectMasked: true,
},
{
name: "Short OpenAI Key",
input: "sk-1234567890",
expectedOutput: "sk-****7890",
expectMasked: true,
},
{
name: "AWS Access Key",
input: "AKIAIOSFODNN7EXAMPLE",
expectedOutput: "AKIA****EXAMPLE",
expectMasked: true,
},
{
name: "Normal Text",
input: "This is normal text",
expectedOutput: "This is normal text",
expectMasked: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := sanitizer.Mask(tc.input)
if tc.expectMasked {
assert.NotEqual(t, tc.input, result, "Expected masking for: %s", tc.name)
assert.Contains(t, result, "****", "Expected **** in masked result for: %s", tc.name)
} else {
assert.Equal(t, tc.expectedOutput, result, "Expected unchanged for: %s", tc.name)
}
})
}
}
func TestSanitizer_Scan_ResponseBody(t *testing.T) {
// 检测响应体中的凭证泄露
scanner := NewCredentialScanner()
responseBody := `{
"success": true,
"data": {
"api_key": "sk-1234567890abcdefghijklmnopqrstuvwxyz",
"user": "testuser"
}
}`
result := scanner.Scan(responseBody)
assert.True(t, result.HasViolation())
assert.NotEmpty(t, result.Violations)
// 验证找到了api_key类型的违规
foundTypes := make([]string, 0)
for _, v := range result.Violations {
foundTypes = append(foundTypes, v.Type)
}
assert.Contains(t, foundTypes, "api_key")
}
func TestSanitizer_MaskMap(t *testing.T) {
// 测试对map进行脱敏
sanitizer := NewSanitizer()
input := map[string]interface{}{
"api_key": "sk-1234567890abcdefghijklmnopqrstuvwxyz",
"secret": "mysecretkey123",
"user": "testuser",
}
masked := sanitizer.MaskMap(input)
// 验证敏感字段被脱敏
assert.NotEqual(t, input["api_key"], masked["api_key"])
assert.NotEqual(t, input["secret"], masked["secret"])
assert.Equal(t, input["user"], masked["user"])
// 验证脱敏格式
assert.Contains(t, masked["api_key"], "****")
assert.Contains(t, masked["secret"], "****")
}
func TestSanitizer_MaskSlice(t *testing.T) {
// 测试对slice进行脱敏
sanitizer := NewSanitizer()
input := []string{
"sk-1234567890abcdefghijklmnopqrstuvwxyz",
"normal text",
"password123",
}
masked := sanitizer.MaskSlice(input)
assert.Len(t, masked, 3)
assert.NotEqual(t, input[0], masked[0])
assert.Equal(t, input[1], masked[1])
assert.NotEqual(t, input[2], masked[2])
}
func TestCredentialScanner_SensitiveFields(t *testing.T) {
// 测试敏感字段列表
fields := GetSensitiveFields()
// 验证常见敏感字段
assert.Contains(t, fields, "api_key")
assert.Contains(t, fields, "secret")
assert.Contains(t, fields, "password")
assert.Contains(t, fields, "token")
assert.Contains(t, fields, "access_key")
assert.Contains(t, fields, "private_key")
}
func TestCredentialScanner_ScanRules(t *testing.T) {
// 测试扫描规则
scanner := NewCredentialScanner()
rules := scanner.GetRules()
assert.NotEmpty(t, rules, "Scanner should have rules")
// 验证规则有ID和描述
for _, rule := range rules {
assert.NotEmpty(t, rule.ID)
assert.NotEmpty(t, rule.Description)
}
}
func TestSanitizer_IsSensitiveField(t *testing.T) {
// 测试字段名敏感性判断
testCases := []struct {
fieldName string
expected bool
}{
{"api_key", true},
{"secret", true},
{"password", true},
{"token", true},
{"access_key", true},
{"private_key", true},
{"session_id", true},
{"authorization", true},
{"user", false},
{"name", false},
{"email", false},
{"id", false},
}
for _, tc := range testCases {
t.Run(tc.fieldName, func(t *testing.T) {
result := IsSensitiveField(tc.fieldName)
assert.Equal(t, tc.expected, result, "Field %s sensitivity mismatch", tc.fieldName)
})
}
}
func TestSanitizer_ScanLog(t *testing.T) {
// 测试日志扫描
scanner := NewCredentialScanner()
logLine := `2026-04-02 10:30:45 INFO [api] Request completed api_key=sk-1234567890abcdefghijklmnopqrstuvwxyz duration=100ms`
result := scanner.Scan(logLine)
assert.True(t, result.HasViolation())
assert.NotEmpty(t, result.Violations)
// sk-开头的key会被识别为openai_key
assert.Equal(t, "openai_key", result.Violations[0].Type)
}
func TestSanitizer_MultipleViolations(t *testing.T) {
// 测试多个违规
scanner := NewCredentialScanner()
content := `{
"api_key": "sk-1234567890abcdefghijklmnopqrstuvwxyz",
"secret_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"password": "mysecretpassword"
}`
result := scanner.Scan(content)
assert.True(t, result.HasViolation())
assert.GreaterOrEqual(t, len(result.Violations), 3)
}
// P2-03: regexp.MustCompile可能panic应该使用regexp.Compile并处理错误
func TestP2_03_NewCredentialScanner_InvalidRegex(t *testing.T) {
// 测试一个无效的正则表达式
// 由于NewCredentialScanner内部使用MustCompile这里我们测试在初始化时是否会panic
// 创建一个会panic的场景无效正则应该被Compile检测而不是MustCompile
// 通过检查NewCredentialScanner是否能正常创建不panic来验证
defer func() {
if r := recover(); r != nil {
t.Errorf("P2-03 BUG: NewCredentialScanner panicked with invalid regex: %v", r)
}
}()
// 这里如果正则都是有效的应该不会panic
scanner := NewCredentialScanner()
if scanner == nil {
t.Error("scanner should not be nil")
}
// 但我们无法在测试中模拟无效正则因为MustCompile在编译时就panic了
// 所以这个测试更多是文档性质的
t.Logf("P2-03: NewCredentialScanner uses MustCompile which panics on invalid regex - should use Compile with error handling")
}
// P2-03: 验证MustCompile在无效正则时会panic
// 这个测试演示了问题使用无效正则会导致panic
func TestP2_03_MustCompile_PanicsOnInvalidRegex(t *testing.T) {
invalidRegex := "[invalid" // 无效的正则,缺少结束括号
defer func() {
if r := recover(); r != nil {
t.Logf("P2-03 CONFIRMED: MustCompile panics on invalid regex: %v", r)
}
}()
// 这行会panic
_ = regexp.MustCompile(invalidRegex)
t.Error("Should have panicked")
}