fix: recognize content ownership within prefixed error chains
This commit is contained in:
@@ -115,6 +115,7 @@ func redactForm(body string) (string, bool) {
|
||||
}
|
||||
|
||||
var textFieldLine = regexp.MustCompile(`^\s*([A-Za-z_][A-Za-z0-9_.-]*)\s*[:=]`)
|
||||
var textFieldLabel = regexp.MustCompile(`\b([A-Za-z_][A-Za-z0-9_.-]*)\s*[:=]`)
|
||||
|
||||
func redactConversationLines(body string) string {
|
||||
lines := strings.SplitAfter(body, "\n")
|
||||
@@ -133,14 +134,20 @@ func redactConversationLines(body string) string {
|
||||
}
|
||||
hiding = false
|
||||
}
|
||||
if len(field) > 0 && sensitive(field[1]) {
|
||||
lines[i] = "[REDACTED_FIELD]"
|
||||
// Labels establish ownership wherever they occur in an error chain, not
|
||||
// only at line start. Preserve the preceding actual failure description.
|
||||
for _, span := range textFieldLabel.FindAllStringSubmatchIndex(line, -1) {
|
||||
key := line[span[2]:span[3]]
|
||||
if !sensitive(key) {
|
||||
continue
|
||||
}
|
||||
lines[i] = line[:span[0]] + "[REDACTED_FIELD]"
|
||||
if strings.HasSuffix(line, "\n") {
|
||||
lines[i] += "\n"
|
||||
}
|
||||
// Credentials/header fields own their single line, not unrelated
|
||||
// following diagnostics. Only conversational fields own continuation.
|
||||
hiding = conversationField(field[1])
|
||||
// Credentials own only this line; content owns the following paragraphs.
|
||||
hiding = conversationField(key)
|
||||
break
|
||||
}
|
||||
}
|
||||
return strings.Join(lines, "")
|
||||
|
||||
@@ -61,6 +61,31 @@ func TestContentFieldClassificationAndEveryLoggingPathAgree(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefixedContentLabelsKeepReasonAndOwnFollowingDialogue(t *testing.T) {
|
||||
for _, prefix := range []string{"upstream failed: ", "wrapped error: upstream failed: ", "upstream failed; ", "上游失败:upstream failed: "} {
|
||||
for _, field := range []string{"conversation", "history", "messages", "system_message", "prompt", "final_transcript"} {
|
||||
value := prefix + field + ":\n\nuser: private-user-turn\n\nassistant: private-assistant-turn\nstatus=retry_pending\nattempts=2"
|
||||
for _, debug := range []bool{false, true} {
|
||||
out := installTestLogger(t, debug)
|
||||
slog.Error("request 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("prefix %q field %s debug %v leak: %s", prefix, field, debug, text)
|
||||
}
|
||||
}
|
||||
for _, keep := range []string{"upstream failed", "retry_pending", "attempts=2"} {
|
||||
if !strings.Contains(text, keep) {
|
||||
t.Fatal("business reason lost", text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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"} {
|
||||
|
||||
Reference in New Issue
Block a user