Files
lijiaoqiao/supply-api/internal/audit/sanitizer/sanitizer.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

290 lines
6.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 sanitizer
import (
"regexp"
"strings"
)
// ScanRule 扫描规则
type ScanRule struct {
ID string
Pattern *regexp.Regexp
Description string
Severity string
}
// Violation 违规项
type Violation struct {
Type string // 违规类型
Pattern string // 匹配的正则模式
Value string // 匹配的值(已脱敏)
Description string
}
// ScanResult 扫描结果
type ScanResult struct {
Violations []Violation
Passed bool
}
// NewScanResult 创建扫描结果
func NewScanResult() *ScanResult {
return &ScanResult{
Violations: []Violation{},
Passed: true,
}
}
// HasViolation 检查是否有违规
func (r *ScanResult) HasViolation() bool {
return len(r.Violations) > 0
}
// AddViolation 添加违规项
func (r *ScanResult) AddViolation(v Violation) {
r.Violations = append(r.Violations, v)
r.Passed = false
}
// CredentialScanner 凭证扫描器
type CredentialScanner struct {
rules []ScanRule
}
// compileRegex 安全编译正则表达式避免panic
func compileRegex(pattern string) *regexp.Regexp {
re, err := regexp.Compile(pattern)
if err != nil {
// 如果编译失败使用一个永远不会匹配的pattern
// 这样可以避免panic同时让扫描器继续工作
return regexp.MustCompile("(?!)")
}
return re
}
// NewCredentialScanner 创建凭证扫描器
func NewCredentialScanner() *CredentialScanner {
scanner := &CredentialScanner{
rules: []ScanRule{
{
ID: "openai_key",
Pattern: compileRegex(`sk-[a-zA-Z0-9]{20,}`),
Description: "OpenAI API Key",
Severity: "HIGH",
},
{
ID: "api_key",
Pattern: compileRegex(`(?i)(api[_-]?key|apikey)["\s:=]+['"]?([a-zA-Z0-9_\-]{16,})['"]?`),
Description: "Generic API Key",
Severity: "MEDIUM",
},
{
ID: "aws_access_key",
Pattern: compileRegex(`(?i)(access[_-]?key[_-]?id|aws[_-]?access[_-]?key)["\s:=]+['"]?(AKIA[0-9A-Z]{16})['"]?`),
Description: "AWS Access Key ID",
Severity: "HIGH",
},
{
ID: "aws_secret_key",
Pattern: compileRegex(`(?i)(secret[_-]?key|aws[_-]?.*secret[_-]?key)["\s:=]+['"]?([a-zA-Z0-9/+=]{40})['"]?`),
Description: "AWS Secret Access Key",
Severity: "HIGH",
},
{
ID: "password",
Pattern: compileRegex(`(?i)(password|passwd|pwd)["\s:=]+['"]?([a-zA-Z0-9@#$%^&*!]{8,})['"]?`),
Description: "Password",
Severity: "HIGH",
},
{
ID: "bearer_token",
Pattern: compileRegex(`(?i)(token|bearer|authorization)["\s:=]+['"]?([Bb]earer\s+)?([a-zA-Z0-9_\-\.]+)['"]?`),
Description: "Bearer Token",
Severity: "MEDIUM",
},
{
ID: "private_key",
Pattern: compileRegex(`-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----`),
Description: "Private Key",
Severity: "CRITICAL",
},
{
ID: "secret",
Pattern: compileRegex(`(?i)(secret|client[_-]?secret)["\s:=]+['"]?([a-zA-Z0-9_\-]{16,})['"]?`),
Description: "Secret",
Severity: "HIGH",
},
},
}
return scanner
}
// Scan 扫描内容
func (s *CredentialScanner) Scan(content string) *ScanResult {
result := NewScanResult()
for _, rule := range s.rules {
matches := rule.Pattern.FindAllStringSubmatch(content, -1)
for _, match := range matches {
// 构建违规项
violation := Violation{
Type: rule.ID,
Pattern: rule.Pattern.String(),
Description: rule.Description,
}
// 提取匹配的值(取最后一个匹配组)
if len(match) > 1 {
violation.Value = maskString(match[len(match)-1])
} else {
violation.Value = maskString(match[0])
}
result.AddViolation(violation)
}
}
return result
}
// GetRules 获取扫描规则
func (s *CredentialScanner) GetRules() []ScanRule {
return s.rules
}
// Sanitizer 脱敏器
type Sanitizer struct {
patterns []*regexp.Regexp
}
// NewSanitizer 创建脱敏器
func NewSanitizer() *Sanitizer {
return &Sanitizer{
patterns: []*regexp.Regexp{
// OpenAI API Key
compileRegex(`(sk-[a-zA-Z0-9]{4})[a-zA-Z0-9]+([a-zA-Z0-9]{4})`),
// AWS Access Key
compileRegex(`(AKIA[0-9A-Z]{4})[0-9A-Z]+([0-9A-Z]{4})`),
// Generic API Key
compileRegex(`([a-zA-Z0-9_\-]{4})[a-zA-Z0-9_\-]{8,}([a-zA-Z0-9_\-]{4})`),
// Password
compileRegex(`([a-zA-Z0-9@#$%^&*!]{4})[a-zA-Z0-9@#$%^&*!]+([a-zA-Z0-9@#$%^&*!]{4})`),
},
}
}
// Mask 对字符串进行脱敏
func (s *Sanitizer) Mask(content string) string {
result := content
for _, pattern := range s.patterns {
// 替换为格式前4字符 + **** + 后4字符
result = pattern.ReplaceAllStringFunc(result, func(match string) string {
// 尝试分组替换
re := compileRegex(`^(.{4}).+(.{4})$`)
submatch := re.FindStringSubmatch(match)
if len(submatch) == 3 {
return submatch[1] + "****" + submatch[2]
}
// 如果无法分组,直接掩码
if len(match) > 8 {
return match[:4] + "****" + match[len(match)-4:]
}
return "****"
})
}
return result
}
// MaskMap 对map进行脱敏
func (s *Sanitizer) MaskMap(data map[string]interface{}) map[string]interface{} {
result := make(map[string]interface{})
for key, value := range data {
if IsSensitiveField(key) {
if str, ok := value.(string); ok {
result[key] = s.Mask(str)
} else {
result[key] = value
}
} else {
result[key] = s.maskValue(value)
}
}
return result
}
// MaskSlice 对slice进行脱敏
func (s *Sanitizer) MaskSlice(data []string) []string {
result := make([]string, len(data))
for i, item := range data {
result[i] = s.Mask(item)
}
return result
}
// maskValue 递归掩码
func (s *Sanitizer) maskValue(value interface{}) interface{} {
switch v := value.(type) {
case string:
return s.Mask(v)
case map[string]interface{}:
return s.MaskMap(v)
case []interface{}:
result := make([]interface{}, len(v))
for i, item := range v {
result[i] = s.maskValue(item)
}
return result
case []string:
return s.MaskSlice(v)
default:
return v
}
}
// maskString 掩码字符串
func maskString(s string) string {
if len(s) > 8 {
return s[:4] + "****" + s[len(s)-4:]
}
return "****"
}
// GetSensitiveFields 获取敏感字段列表
func GetSensitiveFields() []string {
return []string{
"api_key",
"apikey",
"secret",
"secret_key",
"password",
"passwd",
"pwd",
"token",
"access_key",
"access_key_id",
"private_key",
"session_id",
"authorization",
"bearer",
"client_secret",
"credentials",
}
}
// IsSensitiveField 判断字段名是否为敏感字段
func IsSensitiveField(fieldName string) bool {
lowerName := strings.ToLower(fieldName)
sensitiveFields := GetSensitiveFields()
for _, sf := range sensitiveFields {
if strings.Contains(lowerName, sf) {
return true
}
}
return false
}