diff --git a/internal/logs/sanitizer.go b/internal/logs/sanitizer.go index f450866e8..b99c5decb 100644 --- a/internal/logs/sanitizer.go +++ b/internal/logs/sanitizer.go @@ -52,10 +52,12 @@ func (s *SecretSanitizer) registerDefaultPatterns() { }, }) - // OpenAI API keys (sk-...) + // OpenAI API keys: legacy sk-..., and the current sk-proj-/sk-svcacct-/ + // sk-admin- forms whose suffix also carries '_' and '-'. The legacy branch + // stays alphanumeric so sk-ant- keys still reach anthropic_key below. s.patterns = append(s.patterns, &secretPattern{ name: "openai_key", - regex: regexp.MustCompile(`\b(sk-[A-Za-z0-9]{20,})\b`), + regex: regexp.MustCompile(`\b(sk-(?:(?:proj|svcacct|admin)-[A-Za-z0-9_\-]{20,}|[A-Za-z0-9]{20,}))\b`), maskFunc: func(key string) string { if len(key) <= 5 { return "****" diff --git a/internal/logs/sanitizer_openai_project_key_test.go b/internal/logs/sanitizer_openai_project_key_test.go new file mode 100644 index 000000000..dd070115f --- /dev/null +++ b/internal/logs/sanitizer_openai_project_key_test.go @@ -0,0 +1,27 @@ +package logs + +import ( + "strings" + "testing" +) + +func TestSanitizer_OpenAIProjectAndServiceAccountKeys(t *testing.T) { + s := newTestSanitizer() + for _, key := range []string{ + "sk-proj-AbCdEf1234567890_GhIjKl-MnOpQrStUvWxYz0123456789", + "sk-svcacct-AbCdEf1234567890_GhIjKl-MnOpQrStUvWxYz0123456789", + "sk-admin-AbCdEf1234567890GhIjKlMnOpQrStUvWxYz0123456789", + "sk-AbCdEf1234567890GhIjKlMnOpQrStUvWxYz0123456789", // legacy + } { + out := s.sanitizeString("using key " + key + " for upstream") + if strings.Contains(out, key) || strings.Contains(out, key[12:]) { + t.Errorf("OpenAI key leaked: %q", out) + } + } + + // sk-ant- keys keep their own, longer-prefix mask. + ant := "sk-ant-api03-" + strings.Repeat("aB3dE", 8) + if out := s.sanitizeString("key " + ant); !strings.Contains(out, "sk-ant-api***") { + t.Errorf("anthropic key no longer masked by anthropic_key: %q", out) + } +}