Sync and async sinks, redaction, and a queryable HTTP service keep concurrent logs tied to a trace id. Co-authored-by: Cursor <cursoragent@cursor.com>
57 lines
1.6 KiB
Go
57 lines
1.6 KiB
Go
// Package redact 在日志落盘前去掉常见秘密形状。
|
|
// 规则来自 agent-squad 的事件脱敏,并允许追加自定义正则。
|
|
package redact
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// Redactor 替换秘密子串。宁可多脱,不漏已识别的形状。
|
|
type Redactor struct {
|
|
extra []*regexp.Regexp
|
|
}
|
|
|
|
var (
|
|
reJWT = regexp.MustCompile(`eyJ[A-Za-z0-9_\-]{8,}\.[A-Za-z0-9_\-]{8,}\.[A-Za-z0-9_\-]{8,}`)
|
|
reSKKey = regexp.MustCompile(`(?:sk|rk|pk)-[A-Za-z0-9_\-]{12,}`)
|
|
reBearer = regexp.MustCompile(`(?i)bearer\s+[A-Za-z0-9._\-]{12,}`)
|
|
reAPIKey = regexp.MustCompile(`(?i)(api[_-]?key["':=\s]+)[A-Za-z0-9_\-\.]{12,}`)
|
|
rePass = regexp.MustCompile(`(?i)(password["':=\s]+)[^\s"',}]+`)
|
|
)
|
|
|
|
// New 编译额外正则。任一非法则失败,不返回半成品。
|
|
func New(extra []string) (*Redactor, error) {
|
|
compiled := make([]*regexp.Regexp, 0, len(extra))
|
|
for _, pattern := range extra {
|
|
if strings.TrimSpace(pattern) == "" {
|
|
continue
|
|
}
|
|
re, err := regexp.Compile(pattern)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("redact: pattern %q: %w", pattern, err)
|
|
}
|
|
compiled = append(compiled, re)
|
|
}
|
|
return &Redactor{extra: compiled}, nil
|
|
}
|
|
|
|
// Apply 脱敏一段文本。
|
|
func (r *Redactor) Apply(s string) string {
|
|
if r == nil || s == "" {
|
|
return s
|
|
}
|
|
s = reJWT.ReplaceAllString(s, "***")
|
|
s = reSKKey.ReplaceAllString(s, "***")
|
|
s = reBearer.ReplaceAllString(s, "Bearer ***")
|
|
s = reAPIKey.ReplaceAllString(s, "${1}***")
|
|
if strings.Contains(strings.ToLower(s), "password") {
|
|
s = rePass.ReplaceAllString(s, "${1}***")
|
|
}
|
|
for _, re := range r.extra {
|
|
s = re.ReplaceAllString(s, "***")
|
|
}
|
|
return s
|
|
}
|