дȫÏîĿÉó²éÎÊÌⲢ·¢²¼ v1.1.9
¸ÄÓñ¾»ú¶ÀÁ¢ CA£¬»ָ´ CLI TLS УÑ飬±£»¤ƾ¾ÝÓëÅäÖò¢·¢±£´档 д Cursor Êý¾ݡ¢»Ự·¾¶¡¢´úÀíÉúÃüÖÜÆڡ¢ģÐÍÇëÇóÓë Skills Îļþ²Ù×÷¡£ Ϊ¶ÀÁ¢ Tab ·þÎñÔö¼ӿͻ§¶˼øȨ£¬Éý¼¶ÒÀÀµÓ빤¾ßÁ´£¬ÍêÉƷ¢²¼ԪÐÅϢͬ²½¡£ Ñé֤£ºÁ½¸ö Go ģ¿éµIJâÊÔ/vet¡¢¹ؼüģ¿é race¡¢ǰ¶˲âÊÔ/Éú²ú¹¹½¨/Éó¼ơ¢ Go ¿ɴï©¶´ɨÃèÓë macOS/Linux ¿ÉÐÐģ¿齻²æ±àÒë¾ùͨ¹ý¡£ Windows ¸ôÀëÆô¶¯ͨ¹ý£»δÍê³ɵĵã»÷ðÑ̺ÍԭÉúƽ̨Ñé֤¼ûÉó²鱨¸档
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
"time"
|
||||
|
||||
"cursor/internal/appdata"
|
||||
"cursor/internal/securefile"
|
||||
|
||||
"github.com/lmittmann/tint"
|
||||
"github.com/mattn/go-colorable"
|
||||
@@ -50,7 +51,7 @@ func Init() {
|
||||
if len(handlers) > 1 {
|
||||
handler = &multiHandler{handlers: handlers}
|
||||
}
|
||||
slog.SetDefault(slog.New(handler))
|
||||
slog.SetDefault(slog.New(redactingHandler{handler}))
|
||||
stdlog.SetFlags(0)
|
||||
if logFilePath != "" {
|
||||
slog.Info("应用日志已写入文件", "path", logFilePath, "pid", os.Getpid())
|
||||
@@ -180,10 +181,14 @@ func (writer *lineWindowFileWriter) Write(payload []byte) (int, error) {
|
||||
}
|
||||
|
||||
func (writer *lineWindowFileWriter) openLocked() error {
|
||||
file, err := os.OpenFile(writer.path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
|
||||
file, err := os.OpenFile(writer.path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := securefile.Protect(writer.path); err != nil {
|
||||
file.Close()
|
||||
return err
|
||||
}
|
||||
writer.file = file
|
||||
logFile = file
|
||||
return nil
|
||||
@@ -204,7 +209,7 @@ func (writer *lineWindowFileWriter) trimToLastLinesLocked(targetLines int) error
|
||||
return err
|
||||
}
|
||||
trimmed, lineCount := lastLinesBytes(payload, targetLines)
|
||||
if err := os.WriteFile(writer.path, trimmed, 0o644); err != nil {
|
||||
if err := os.WriteFile(writer.path, trimmed, 0o600); err != nil {
|
||||
if reopenErr := writer.openLocked(); reopenErr != nil {
|
||||
return errors.Join(err, reopenErr)
|
||||
}
|
||||
|
||||
85
internal/logger/redact.go
Normal file
85
internal/logger/redact.go
Normal file
@@ -0,0 +1,85 @@
|
||||
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)}
|
||||
}
|
||||
21
internal/logger/redact_test.go
Normal file
21
internal/logger/redact_test.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSecretsRedactedInMessagesErrorsAttributesAndGroups(t *testing.T) {
|
||||
RegisterSecrets("SENTINEL_PRIVATE_KEY")
|
||||
var buffer bytes.Buffer
|
||||
log := slog.New(redactingHandler{slog.NewJSONHandler(&buffer, nil)})
|
||||
log.With("token", "BOUND_SECRET").Info("provider echoed SENTINEL_PRIVATE_KEY", "error", errors.New("Authorization: Bearer UNKNOWN_SECRET"), "payload", map[string]string{"config": "MAP_SECRET"}, "group", slog.GroupValue(slog.String("apiKey", "GROUP_SECRET")))
|
||||
for _, secret := range []string{"SENTINEL_PRIVATE_KEY", "BOUND_SECRET", "UNKNOWN_SECRET", "MAP_SECRET", "GROUP_SECRET"} {
|
||||
if strings.Contains(buffer.String(), secret) {
|
||||
t.Fatalf("leaked secret %s", secret)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user