Files
Local-Cursor/internal/logger/redact.go

86 lines
2.5 KiB
Go
Raw Normal View History

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)}
}