package logger import ( "context" "fmt" "log/slog" "regexp" "sort" "strings" "sync" ) var secretRegistry = struct { sync.RWMutex values map[string]struct{} }{values: map[string]struct{}{}} var bearerPattern = regexp.MustCompile(`(?i)Bearer\s+[^\s"'<>]+`) var credentialPattern = regexp.MustCompile(`(?i)((?:api[_-]?key|token|authorization|cookie|password|secret)["']?\s*[:=]\s*["']?)[^"'\s,;}]+`) func RegisterSecrets(values ...string) { secretRegistry.Lock() defer secretRegistry.Unlock() for _, v := range values { if v != "" { secretRegistry.values[v] = struct{}{} } } } func redact(s string) string { secretRegistry.RLock() values := make([]string, 0, len(secretRegistry.values)) for value := range secretRegistry.values { values = append(values, value) } secretRegistry.RUnlock() sort.Slice(values, func(i, j int) bool { return len(values[i]) > len(values[j]) }) for _, value := range values { s = strings.ReplaceAll(s, value, "[redacted]") } s = bearerPattern.ReplaceAllString(s, "Bearer [redacted]") return credentialPattern.ReplaceAllString(s, "${1}[redacted]") } type redactingHandler struct{ next slog.Handler } func (h redactingHandler) Enabled(ctx context.Context, level slog.Level) bool { return h.next.Enabled(ctx, level) } func redactAttr(a slog.Attr) slog.Attr { key := strings.ToLower(a.Key) for _, part := range []string{"apikey", "api_key", "token", "password", "secret", "authorization", "cookie", "payload", "headers", "body", "config"} { if strings.Contains(key, part) { return slog.String(a.Key, "[redacted]") } } v := a.Value.Resolve() switch v.Kind() { case slog.KindString: a.Value = slog.StringValue(redact(v.String())) case slog.KindAny: a.Value = slog.StringValue(redact(fmt.Sprint(v.Any()))) case slog.KindGroup: attrs := v.Group() for i := range attrs { attrs[i] = redactAttr(attrs[i]) } a.Value = slog.GroupValue(attrs...) } return a } func (h redactingHandler) Handle(ctx context.Context, r slog.Record) error { next := slog.NewRecord(r.Time, r.Level, redact(r.Message), r.PC) r.Attrs(func(a slog.Attr) bool { next.AddAttrs(redactAttr(a)); return true }) return h.next.Handle(ctx, next) } func (h redactingHandler) WithAttrs(attrs []slog.Attr) slog.Handler { copy := append([]slog.Attr(nil), attrs...) for i := range copy { copy[i] = redactAttr(copy[i]) } return redactingHandler{h.next.WithAttrs(copy)} } func (h redactingHandler) WithGroup(name string) slog.Handler { return redactingHandler{h.next.WithGroup(name)} }