Files
lijiaoqiao/supply-api/internal/security/query_key_whitelist.go
Your Name 7280ef565c test: improve coverage for audit/events and security modules
- audit/events: 73.5% → 97.6% (+24.1%)
  - Add tests for IsM013/M014/M015RelatedEvent
  - Add tests for FormatSECURITYEvent
  - Add comprehensive coverage for all CRED and SECURITY event functions

- security: 67.2% → 88.8% (+21.6%)
  - Add tests for ValidateKeyID, DecryptionError.Error()
  - Add tests for ValidateQueryParams, GetAllowedParamNames
  - Add tests for isHexString, looksLikeAPIKey
  - Fix test cases to match actual implementation behavior

- audit/sanitizer: Fix MaskMap []string handling bug
  - Add maskSliceInterface for []interface{} type
  - Tests now pass for string slice sensitive fields

All tests pass
2026-04-08 09:00:29 +08:00

237 lines
5.9 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 security
import (
"net/url"
"strings"
)
// ==================== P0-04 Query Key白名单检测 ====================
// AllowedQueryParam 白名单参数
type AllowedQueryParam struct {
Name string
Description string
}
// GetAllowedQueryParams 获取允许的query参数白名单
func GetAllowedQueryParams() []AllowedQueryParam {
return []AllowedQueryParam{
// 分页
{Name: "page", Description: "页码"},
{Name: "page_size", Description: "每页大小"},
{Name: "limit", Description: "限制数量"},
{Name: "offset", Description: "偏移量"},
// 排序
{Name: "sort", Description: "排序字段"},
{Name: "order", Description: "排序方向"},
// 过滤
{Name: "filter", Description: "过滤条件"},
{Name: "search", Description: "搜索关键词"},
// 时间范围
{Name: "start_date", Description: "开始日期"},
{Name: "end_date", Description: "结束日期"},
{Name: "from", Description: "起始时间"},
{Name: "to", Description: "结束时间"},
// 视图选项
{Name: "format", Description: "响应格式"},
{Name: "fields", Description: "字段选择"},
// 调试选项
{Name: "debug", Description: "调试模式"},
}
}
// GetAllowedParamNames 获取白名单参数名集合
func GetAllowedParamNames() map[string]bool {
params := GetAllowedQueryParams()
allowed := make(map[string]bool)
for _, p := range params {
allowed[strings.ToLower(p.Name)] = true
}
return allowed
}
// isQueryParamAllowed 检查参数是否在白名单中(大小写不敏感)
func isQueryParamAllowed(param string, whitelist []AllowedQueryParam) bool {
lowerParam := strings.ToLower(param)
lowerWhitelist := make(map[string]bool)
for _, p := range whitelist {
lowerWhitelist[strings.ToLower(p.Name)] = true
}
return lowerWhitelist[lowerParam]
}
// isQueryParamBlocked 检查参数是否被禁止(大小写不敏感)
func isQueryParamBlocked(param string, whitelist []AllowedQueryParam) bool {
return !isQueryParamAllowed(param, whitelist)
}
// blockedParamNames 禁止的参数名(包含各种变体)
var blockedParamNames = []string{
"key", "api_key", "apikey", "api-key",
"token", "access_token", "access-token",
"refresh_token", "refresh-token",
"secret", "secret_key", "secretkey",
"password", "passwd", "pwd",
"credential", "cred",
"auth", "authorization",
"session", "session_id", "sessionid",
"jwt", "jti",
"signature", "sig",
"private", "private_key", "privatekey",
}
// detectBlockedParams 检测是否有被禁止的参数支持URL解码和大小写不敏感
func detectBlockedParams(query url.Values, whitelist []AllowedQueryParam) bool {
whitelistMap := make(map[string]bool)
for _, p := range whitelist {
whitelistMap[strings.ToLower(p.Name)] = true
}
for param := range query {
// 1. 检查白名单
if whitelistMap[strings.ToLower(param)] {
continue
}
// 2. 检查是否包含敏感关键词(即使参数名不同)
lowerParam := strings.ToLower(param)
if containsSensitiveKeyword(lowerParam) {
return true
}
// 3. 检查参数值是否可疑
value := query.Get(param)
if isSuspiciousQueryValue(param, value) {
return true
}
}
return false
}
// containsSensitiveKeyword 检查是否包含敏感关键词
func containsSensitiveKeyword(param string) bool {
sensitiveKeywords := []string{
"key", "token", "secret", "password", "credential",
"auth", "jwt", "signature", "private",
}
for _, kw := range sensitiveKeywords {
if strings.Contains(param, kw) {
return true
}
}
return false
}
// isSuspiciousQueryValue 检查query参数值是否可疑
// 可疑模式值看起来像API key、Bearer token等
func isSuspiciousQueryValue(param, value string) bool {
if value == "" {
return false
}
// 1. 检查JWT格式即使参数名不像token
if strings.HasPrefix(value, "eyJ") && strings.Count(value, ".") == 2 {
return true
}
// 2. 检查Bearer token格式
if strings.HasPrefix(value, "Bearer ") || strings.HasPrefix(value, "bearer ") {
return true
}
// 3. 检查长度 - 可疑的API key通常较长
if len(value) > 20 && looksLikeAPIKey(value) {
return true
}
// 4. 检查参数名是否包含敏感关键词,且值较长
lowerParam := strings.ToLower(param)
if len(value) > 20 {
if containsSensitiveKeyword(lowerParam) {
return true
}
}
return false
}
// looksLikeAPIKey 检查值是否像API key
func looksLikeAPIKey(value string) bool {
// 常见的API key前缀
apiKeyPrefixes := []string{
"sk-", "sk_", // OpenAI
"ak-", "ak_", // AWS
"pk-", "pk_", // Stripe
"ghp_", "github_", // GitHub
"xoxb-", // Slack
"AIza", // Google API
}
lowerValue := strings.ToLower(value)
for _, prefix := range apiKeyPrefixes {
if strings.HasPrefix(lowerValue, prefix) {
return true
}
}
// 检查是否是长哈希值 (32+字符的十六进制)
if len(value) >= 32 && isHexString(value) {
return true
}
return false
}
// isHexString 检查字符串是否是十六进制
func isHexString(s string) bool {
for _, c := range s {
if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) {
return false
}
}
return len(s) >= 32
}
// QueryKeyValidationResult Query Key验证结果
type QueryKeyValidationResult struct {
Allowed bool
BlockedParam string
Reason string
}
// ValidateQueryParams 验证query参数
func ValidateQueryParams(rawQuery string) *QueryKeyValidationResult {
parsed, err := url.ParseQuery(rawQuery)
if err != nil {
return &QueryKeyValidationResult{
Allowed: false,
Reason: "invalid query string",
}
}
whitelist := GetAllowedQueryParams()
if detectBlockedParams(parsed, whitelist) {
// 找出被阻止的参数
for param := range parsed {
if !isQueryParamAllowed(param, whitelist) {
return &QueryKeyValidationResult{
Allowed: false,
BlockedParam: param,
Reason: "query parameter not in whitelist or suspicious",
}
}
}
}
return &QueryKeyValidationResult{
Allowed: true,
}
}