diff --git a/internal/logging/logging_test.go b/internal/logging/logging_test.go index c972350..d4d2a91 100644 --- a/internal/logging/logging_test.go +++ b/internal/logging/logging_test.go @@ -105,6 +105,19 @@ func TestUnstructuredAndEmbeddedJSONCannotLeakCredentials(t *testing.T) { } } +func TestPrivateKeyAndSignedWebSocketURLNeverLeakInEitherMode(t *testing.T) { + for _, debug := range []bool{false, true} { + out := installTestLogger(t, debug) + slog.Info("workflow", "private_key", "private-key-value", "client_key_pem", "private-pem-value", "endpoint", "wss://user:private-password@host/ws?Signature=private-signature") + Body(context.Background(), "HTTP data", []byte(`{"privateKey":"private-key-value","ws_endpoint":"wss://host/ws?Signature=private-signature"}`)) + for _, secret := range []string{"private-key-value", "private-pem-value", "private-password", "private-signature"} { + if strings.Contains(out.String(), secret) { + t.Fatalf("leaked %s: %s", secret, out.String()) + } + } + } +} + func TestSensitiveWorkflowFieldsAndURLsNeverLeak(t *testing.T) { out := installTestLogger(t, true) slog.Info("workflow", "Authorization", "Bearer do-not-print", "apiKey", "do-not-print", "prompt", "do-not-print", "url", "https://user:pass@host/path?signature=do-not-print") diff --git a/internal/logging/redact.go b/internal/logging/redact.go index 25bb02f..0682a49 100644 --- a/internal/logging/redact.go +++ b/internal/logging/redact.go @@ -23,7 +23,7 @@ func sensitive(key string) bool { return -1 }, key) switch normalized { - case "content", "headers", "history", "sentence", "sentences", "words", "openingremark", "openingremarks", "greeting", "sessionnonce", "credential", "credentials", "key", "authorization", "proxyauthorization", "apikey", "accesskeyid", "accesskeysecret", "appkey", "appsecret", "secret", "secretkey", "password", "passwd", "token", "accesstoken", "refreshtoken", "uploadtoken", "providertoken", "sessioncredential", "clientnonce", "servernonce", "cookie", "setcookie", "prompt", "systemprompt", "instructions", "messages", "messagecontent", "transcript", "transcription", "finaltranscript", "text", "utterance", "audio", "audiodata", "recordingdata", "raw", "closingremark", "signature", "targeturl": + case "privatekey", "privatekeypem", "clientkey", "clientkeypem", "serverkey", "serverkeypem", "content", "headers", "history", "sentence", "sentences", "words", "openingremark", "openingremarks", "greeting", "sessionnonce", "credential", "credentials", "key", "authorization", "proxyauthorization", "apikey", "accesskeyid", "accesskeysecret", "appkey", "appsecret", "secret", "secretkey", "password", "passwd", "token", "accesstoken", "refreshtoken", "uploadtoken", "providertoken", "sessioncredential", "clientnonce", "servernonce", "cookie", "setcookie", "prompt", "systemprompt", "instructions", "messages", "messagecontent", "transcript", "transcription", "finaltranscript", "text", "utterance", "audio", "audiodata", "recordingdata", "raw", "closingremark", "signature", "targeturl": return true } return strings.HasSuffix(normalized, "password") || strings.HasSuffix(normalized, "secret") || strings.HasSuffix(normalized, "apikey") || strings.HasSuffix(normalized, "token") || strings.HasSuffix(normalized, "text") @@ -139,9 +139,9 @@ func RedactJSON(body []byte) (string, error) { } var ( - urlInText = regexp.MustCompile(`https?://[^\s<>"']+`) + urlInText = regexp.MustCompile(`(?:https?|wss?)://[^\s<>"']+`) bearerInText = regexp.MustCompile(`(?i)\bBearer\s+[^\s,;"']+`) - secretInText = regexp.MustCompile(`(?i)\b(api[_-]?key|access[_-]?key[_-]?(secret|id)|password|token|secret|signature|prompt|system[_-]?prompt|content|transcript|text)["']?\s*[=:]\s*(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^\s,;"']+)`) + secretInText = regexp.MustCompile(`(?i)\b(api[_-]?key|private[_-]?key(?:[_-]?pem)?|client[_-]?key(?:[_-]?pem)?|access[_-]?key[_-]?(secret|id)|password|token|secret|signature|prompt|system[_-]?prompt|content|transcript|text)["']?\s*[=:]\s*(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^\s,;"']+)`) ) // Error messages can contain a provider's JSON response after a text prefix.