package logging import ( "bytes" "errors" "io" "log/slog" "net/http" "net/http/httptest" "strings" "testing" ) func TestFreeTextAuthorizationAndUnlabelledPrivatePEMAreHidden(t *testing.T) { for _, debug := range []bool{false, true} { out := installTestLogger(t, debug) cause := "upstream denied\nAuthorization: Basic dXNlcjpwYXNzd29yZA==\nProxy-Authorization: Digest username=private-user, response=private-response\n-----BEGIN RSA PRIVATE KEY-----\nprivate-pem-material\n-----END RSA PRIVATE KEY-----\nretry attempt failed" slog.Error("workflow failed", "error", errors.New(cause)) for _, secret := range []string{"dXNlcjpwYXNzd29yZA==", "private-user", "private-response", "private-pem-material"} { if strings.Contains(out.String(), secret) { t.Fatalf("leak %s: %s", secret, out.String()) } } for _, keep := range []string{"upstream denied", "retry attempt failed"} { if !strings.Contains(out.String(), keep) { t.Fatal("failure reason lost", out.String()) } } } } func TestJSONErrorAuthorizationDoesNotHideBusinessFields(t *testing.T) { out := installTestLogger(t, false) slog.Error("request failed", "error", errors.New(`denied: {"Authorization":"Basic dXNlcjpwYXNzd29yZA==","task_id":"4","reason":"quota full"}`)) text := out.String() if strings.Contains(text, "dXNlcjpwYXNzd29yZA==") || !strings.Contains(text, "quota full") || !strings.Contains(text, "task_id") { t.Fatal(text) } } func TestReadableMarkupFormAndMultilineConversationAreSanitized(t *testing.T) { cases := []struct { body string keep string secrets []string }{ {`readyprivate-markup-keyprivate-markup-prompt27`, "27", []string{"private-markup-key", "private-markup-prompt", "private-input-password"}}, {"status=ready&api_key=private-form-key&remaining=27", "remaining=27", []string{"private-form-key"}}, {"status=ready\nprompt:\n private-multiline-prompt\n still-private\n\nremaining=27", "remaining=27", []string{"private-multiline-prompt", "still-private"}}, } for _, tc := range cases { safe, err := RedactText([]byte(tc.body)) if err != nil { t.Fatal(err) } if !strings.Contains(safe, tc.keep) { t.Fatal("business content lost", safe) } for _, s := range tc.secrets { if strings.Contains(safe, s) { t.Fatalf("leaked %s: %s", s, safe) } } } } func TestReadablePlainHTTPBodiesAreObservedSafelyWithoutChangingBytes(t *testing.T) { body := "status=ready\ntask_id=4\nremaining=27\nAuthorization: Basic dXNlcjpwYXNzd29yZA==\napi_key=private-key\n-----BEGIN PRIVATE KEY-----\nprivate-pem-material\n-----END PRIVATE KEY-----\nretry_after=60\n" + strings.Repeat("business-data ", 12000) for _, debug := range []bool{false, true} { out := installTestLogger(t, debug) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { b, _ := io.ReadAll(r.Body) if string(b) != body { t.Error("request changed") } w.Header().Set("Content-Type", "text/plain; charset=utf-8") io.WriteString(w, body) })) req, _ := http.NewRequest(http.MethodPost, server.URL, strings.NewReader(body)) req.Header.Set("Content-Type", "text/plain") res, err := HTTPClient(server.Client(), "dispatcher", "saas").Do(req) if err != nil { t.Fatal(err) } got, err := io.ReadAll(res.Body) res.Body.Close() server.Close() if err != nil || !bytes.Equal(got, []byte(body)) { t.Fatal("response changed", err) } text := out.String() for _, secret := range []string{"dXNlcjpwYXNzd29yZA==", "private-key", "private-pem-material"} { if strings.Contains(text, secret) { t.Fatalf("leak %s", secret) } } for _, keep := range []string{"remaining=27", "retry_after=60", "business-data"} { if strings.Contains(text, keep) != debug { t.Fatalf("body absent/truncated or exposed in normal mode: %v %s", debug, keep) } } if debug && !strings.Contains(text, `"body_encoding":"utf-8"`) { t.Fatal("text not identified", text) } } }