| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 |
- package logredact
- import (
- "encoding/json"
- "regexp"
- "sort"
- "strings"
- "sync"
- )
- // maxRedactDepth 限制递归深度以防止栈溢出
- const maxRedactDepth = 32
- var defaultSensitiveKeys = map[string]struct{}{
- "authorization_code": {},
- "code": {},
- "code_verifier": {},
- "access_token": {},
- "refresh_token": {},
- "id_token": {},
- "client_secret": {},
- "password": {},
- }
- var defaultSensitiveKeyList = []string{
- "authorization_code",
- "code",
- "code_verifier",
- "access_token",
- "refresh_token",
- "id_token",
- "client_secret",
- "password",
- }
- type textRedactPatterns struct {
- reJSONLike *regexp.Regexp
- reQueryLike *regexp.Regexp
- rePlain *regexp.Regexp
- }
- var (
- reGOCSPX = regexp.MustCompile(`GOCSPX-[0-9A-Za-z_-]{24,}`)
- reAIza = regexp.MustCompile(`AIza[0-9A-Za-z_-]{35}`)
- defaultTextRedactPatterns = compileTextRedactPatterns(nil)
- extraTextPatternCache sync.Map // map[string]*textRedactPatterns
- )
- func RedactMap(input map[string]any, extraKeys ...string) map[string]any {
- if input == nil {
- return map[string]any{}
- }
- keys := buildKeySet(extraKeys)
- redacted, ok := redactValueWithDepth(input, keys, 0).(map[string]any)
- if !ok {
- return map[string]any{}
- }
- return redacted
- }
- func RedactJSON(raw []byte, extraKeys ...string) string {
- if len(raw) == 0 {
- return ""
- }
- var value any
- if err := json.Unmarshal(raw, &value); err != nil {
- return "<non-json payload redacted>"
- }
- keys := buildKeySet(extraKeys)
- redacted := redactValueWithDepth(value, keys, 0)
- encoded, err := json.Marshal(redacted)
- if err != nil {
- return "<redacted>"
- }
- return string(encoded)
- }
- // RedactText 对非结构化文本做轻量脱敏。
- //
- // 规则:
- // - 如果文本本身是 JSON,则按 RedactJSON 处理。
- // - 否则尝试对常见 key=value / key:"value" 片段做脱敏。
- //
- // 注意:该函数用于日志/错误信息兜底,不保证覆盖所有格式。
- func RedactText(input string, extraKeys ...string) string {
- input = strings.TrimSpace(input)
- if input == "" {
- return ""
- }
- raw := []byte(input)
- if json.Valid(raw) {
- return RedactJSON(raw, extraKeys...)
- }
- patterns := getTextRedactPatterns(extraKeys)
- out := input
- out = reGOCSPX.ReplaceAllString(out, "GOCSPX-***")
- out = reAIza.ReplaceAllString(out, "AIza***")
- out = patterns.reJSONLike.ReplaceAllString(out, `$1***$3`)
- out = patterns.reQueryLike.ReplaceAllString(out, `$1=***`)
- out = patterns.rePlain.ReplaceAllString(out, `$1$2***`)
- return out
- }
- func compileTextRedactPatterns(extraKeys []string) *textRedactPatterns {
- keyAlt := buildKeyAlternation(extraKeys)
- return &textRedactPatterns{
- // JSON-like: "access_token":"..."
- reJSONLike: regexp.MustCompile(`(?i)("(?:` + keyAlt + `)"\s*:\s*")([^"]*)(")`),
- // Query-like: access_token=...
- reQueryLike: regexp.MustCompile(`(?i)\b((?:` + keyAlt + `))=([^&\s]+)`),
- // Plain: access_token: ... / access_token = ...
- rePlain: regexp.MustCompile(`(?i)\b((?:` + keyAlt + `))\b(\s*[:=]\s*)([^,\s]+)`),
- }
- }
- func getTextRedactPatterns(extraKeys []string) *textRedactPatterns {
- normalizedExtraKeys := normalizeAndSortExtraKeys(extraKeys)
- if len(normalizedExtraKeys) == 0 {
- return defaultTextRedactPatterns
- }
- cacheKey := strings.Join(normalizedExtraKeys, ",")
- if cached, ok := extraTextPatternCache.Load(cacheKey); ok {
- if patterns, ok := cached.(*textRedactPatterns); ok {
- return patterns
- }
- }
- compiled := compileTextRedactPatterns(normalizedExtraKeys)
- actual, _ := extraTextPatternCache.LoadOrStore(cacheKey, compiled)
- if patterns, ok := actual.(*textRedactPatterns); ok {
- return patterns
- }
- return compiled
- }
- func normalizeAndSortExtraKeys(extraKeys []string) []string {
- if len(extraKeys) == 0 {
- return nil
- }
- seen := make(map[string]struct{}, len(extraKeys))
- keys := make([]string, 0, len(extraKeys))
- for _, key := range extraKeys {
- normalized := normalizeKey(key)
- if normalized == "" {
- continue
- }
- if _, ok := seen[normalized]; ok {
- continue
- }
- seen[normalized] = struct{}{}
- keys = append(keys, normalized)
- }
- sort.Strings(keys)
- return keys
- }
- func buildKeyAlternation(extraKeys []string) string {
- seen := make(map[string]struct{}, len(defaultSensitiveKeyList)+len(extraKeys))
- keys := make([]string, 0, len(defaultSensitiveKeyList)+len(extraKeys))
- for _, k := range defaultSensitiveKeyList {
- seen[k] = struct{}{}
- keys = append(keys, regexp.QuoteMeta(k))
- }
- for _, k := range extraKeys {
- n := normalizeKey(k)
- if n == "" {
- continue
- }
- if _, ok := seen[n]; ok {
- continue
- }
- seen[n] = struct{}{}
- keys = append(keys, regexp.QuoteMeta(n))
- }
- return strings.Join(keys, "|")
- }
- func buildKeySet(extraKeys []string) map[string]struct{} {
- keys := make(map[string]struct{}, len(defaultSensitiveKeys)+len(extraKeys))
- for k := range defaultSensitiveKeys {
- keys[k] = struct{}{}
- }
- for _, key := range extraKeys {
- normalized := normalizeKey(key)
- if normalized == "" {
- continue
- }
- keys[normalized] = struct{}{}
- }
- return keys
- }
- func redactValueWithDepth(value any, keys map[string]struct{}, depth int) any {
- if depth > maxRedactDepth {
- return "<depth limit exceeded>"
- }
- switch v := value.(type) {
- case map[string]any:
- out := make(map[string]any, len(v))
- for k, val := range v {
- if isSensitiveKey(k, keys) {
- out[k] = "***"
- continue
- }
- out[k] = redactValueWithDepth(val, keys, depth+1)
- }
- return out
- case []any:
- out := make([]any, len(v))
- for i, item := range v {
- out[i] = redactValueWithDepth(item, keys, depth+1)
- }
- return out
- default:
- return value
- }
- }
- func isSensitiveKey(key string, keys map[string]struct{}) bool {
- _, ok := keys[normalizeKey(key)]
- return ok
- }
- func normalizeKey(key string) string {
- return strings.ToLower(strings.TrimSpace(key))
- }
|