From b093f4d0261b1563ffae4b7dfeb3a8c7a4658491 Mon Sep 17 00:00:00 2001 From: Rogee Date: Sat, 10 Oct 2026 01:51:14 +0800 Subject: [PATCH] fix: retain conversation ownership across blank paragraphs --- internal/logging/text.go | 7 ++----- internal/logging/text_security_test.go | 29 ++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/internal/logging/text.go b/internal/logging/text.go index 0524207..c965c42 100644 --- a/internal/logging/text.go +++ b/internal/logging/text.go @@ -130,11 +130,8 @@ func redactConversationLines(body string) string { lines := strings.SplitAfter(body, "\n") hiding := false for i, line := range lines { - trimmed := strings.TrimSpace(line) - if hiding && trimmed == "" { - hiding = false - continue - } + // Whitespace separates paragraphs inside a conversation, not records. + // Ownership ends only at the explicit non-conversation field below. field := textFieldLine.FindStringSubmatch(line) if hiding { // Continuation lines and conversation speaker labels are not business diff --git a/internal/logging/text_security_test.go b/internal/logging/text_security_test.go index 958784c..f8d6f29 100644 --- a/internal/logging/text_security_test.go +++ b/internal/logging/text_security_test.go @@ -29,6 +29,31 @@ func TestAssociatedSensitiveControlsHideContentWithoutSwallowingBusiness(t *test } } +func TestConversationOwnershipPersistsAcrossBlankLines(t *testing.T) { + for _, gap := range []string{"\n", "\n\n", "\n \n\t\n", "\r\n\r\n"} { + for _, label := range []string{"messages", "prompt", "transcript", "content"} { + value := "upstream failed\n" + label + ":" + gap + "user: private-user-turn" + gap + "assistant: private-assistant-turn" + gap + "status=retry_pending\nattempts=2" + for _, debug := range []bool{false, true} { + out := installTestLogger(t, debug) + slog.Error("operation failed", "error", errors.New(value), "detail", value) + slog.Info(value, "stage", "failure_observed") + TextBody(t.Context(), "HTTP text observed", []byte(value), "transport", "http") + text := out.String() + for _, secret := range []string{"private-user-turn", "private-assistant-turn"} { + if strings.Contains(text, secret) { + t.Fatalf("label %s gap %q debug %v leak: %s", label, gap, debug, text) + } + } + for _, keep := range []string{"upstream failed", "retry_pending", "attempts=2"} { + if !strings.Contains(text, keep) { + t.Fatal("business diagnostics lost", text) + } + } + } + } + } +} + func TestFreeTextConversationIsHiddenInBothModesAndAllScalarPaths(t *testing.T) { value := "upstream failed\nmessages:\nuser: private-user-turn\nassistant: private-assistant-turn\n\nstatus=retry_pending\nattempts=2" for _, debug := range []bool{false, true} { @@ -103,7 +128,7 @@ func TestReadableMarkupFormAndMultilineConversationAreSanitized(t *testing.T) { } 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) + body := "status=ready\ntask_id=4\nremaining=27\nmessages:\n\nuser: private-http-user-turn\n\nassistant: private-http-assistant-turn\n\nstatus=ready\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) { @@ -127,7 +152,7 @@ func TestReadablePlainHTTPBodiesAreObservedSafelyWithoutChangingBytes(t *testing t.Fatal("response changed", err) } text := out.String() - for _, secret := range []string{"dXNlcjpwYXNzd29yZA==", "private-key", "private-pem-material"} { + for _, secret := range []string{"dXNlcjpwYXNzd29yZA==", "private-key", "private-pem-material", "private-http-user-turn", "private-http-assistant-turn"} { if strings.Contains(text, secret) { t.Fatalf("leak %s", secret) }