From 8f5e32dca1dee95fca1159e90a99e853dc7534f4 Mon Sep 17 00:00:00 2001 From: weichen Date: Wed, 14 Oct 2026 09:07:07 +0000 Subject: [PATCH] Bound provider terminal labels in evaluation records Why: A provider-controlled finish reason could leak arbitrary content into the supposed content-free audit file. Evidence: Red then green terminal-label regression and full Backend vet/Go test in Dev receipts. Impact: Only evaluation records map unknown terminal labels to a finite class; tool execution rules are unchanged. Refs: U3 r0.5 independent review finding --- .../work_checkpoint_eval_budget_test.go | 35 +++++++++++++++++++ internal/server/work_checkpoint_eval_test.go | 28 +++++++++++++-- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/internal/server/work_checkpoint_eval_budget_test.go b/internal/server/work_checkpoint_eval_budget_test.go index f068ebb..8f0a931 100644 --- a/internal/server/work_checkpoint_eval_budget_test.go +++ b/internal/server/work_checkpoint_eval_budget_test.go @@ -74,6 +74,41 @@ func TestEvaluationRecordsContentFreeTerminal(t *testing.T) { } } +func TestEvaluationRedactsProviderControlledTerminalLabels(t *testing.T) { + dir, _ := newEvaluationEventStore(t) + provider := func(context.Context, string, []agentllm.Message, []agentllm.ToolDecl, agentllm.StreamOptions) <-chan agentllm.Event { + out := make(chan agentllm.Event, 1) + out <- agentllm.Event{Kind: agentllm.KindMessageEnd, + Message: agentllm.Message{Role: agentllm.RoleAssistant, Content: "safe", StopReason: agentllm.StopEnd}, + Usage: agentllm.Usage{PromptTokens: 12, CompletionTokens: 5, TotalTokens: 17}, + FinishReason: "PRIVATE_REASON_R05", StreamComplete: true, + TerminalObservation: &agentllm.TerminalObservation{ReadOutcome: "PRIVATE_OUTCOME_R05", UsagePresent: true}, + } + close(out) + return out + } + for range evalProviderStreamWithProvider(provider, dir)(evalTestContext(t), "fixture", nil, nil, agentllm.StreamOptions{MaxTokens: 4000}) { + } + raw, err := os.ReadFile(filepath.Join(dir, "provider-records.json")) + if err != nil { + t.Fatal(err) + } + if strings.Contains(string(raw), "PRIVATE_REASON_R05") || strings.Contains(string(raw), "PRIVATE_OUTCOME_R05") { + t.Fatal("provider-controlled terminal label escaped into a content-free record") + } + var record struct { + Calls []struct { + FinishReason string `json:"finishReason"` + Observation *agentllm.TerminalObservation `json:"terminalObservation"` + } `json:"calls"` + } + if err := json.Unmarshal(raw, &record); err != nil || len(record.Calls) != 1 || + record.Calls[0].FinishReason != "unknown" || record.Calls[0].Observation == nil || + record.Calls[0].Observation.ReadOutcome != "unknown" { + t.Fatalf("unexpected sanitized terminal labels: %+v err=%v", record, err) + } +} + func TestEvaluationRedactsErrorTerminalAndBlocksTool(t *testing.T) { dir, _ := newEvaluationEventStore(t) var calls atomic.Int32 diff --git a/internal/server/work_checkpoint_eval_test.go b/internal/server/work_checkpoint_eval_test.go index 67ef5d6..dd5bf75 100644 --- a/internal/server/work_checkpoint_eval_test.go +++ b/internal/server/work_checkpoint_eval_test.go @@ -413,6 +413,28 @@ type evalCallRecord struct { FailureClass string `json:"failureClass,omitempty"` } +func evalFinishReasonClass(reason string) string { + switch reason { + case "", "stop", "length", "tool_calls", "function_call", "content_filter": + return reason + default: + return "unknown" + } +} + +func evalTerminalObservation(observation *agentllm.TerminalObservation) *agentllm.TerminalObservation { + if observation == nil { + return nil + } + copy := *observation + switch copy.ReadOutcome { + case "done_marker", "clean_eof", "read_error", "cancelled", "invalid_frame", "provider_error", "transport_error": + default: + copy.ReadOutcome = "unknown" + } + return © +} + func evalProviderStreamWithProvider(provider agentllm.StreamFunc, dir string) agentllm.StreamFunc { batchLimit := 120000 if raw := os.Getenv("WORK_CHECKPOINT_REMAINING_TOKENS"); raw != "" { @@ -565,12 +587,12 @@ func evalProviderStreamWithProvider(provider agentllm.StreamFunc, dir string) ag var terminalObservation *agentllm.TerminalObservation httpStatus := 0 if terminal != nil { - finishReason, streamComplete = terminal.FinishReason, terminal.StreamComplete + finishReason, streamComplete = evalFinishReasonClass(terminal.FinishReason), terminal.StreamComplete returnedUsage = terminal.Usage - terminalObservation = terminal.TerminalObservation + terminalObservation = evalTerminalObservation(terminal.TerminalObservation) httpStatus = terminal.HTTPStatus } else if streamError != nil { - terminalObservation = streamError.TerminalObservation + terminalObservation = evalTerminalObservation(streamError.TerminalObservation) httpStatus = streamError.HTTPStatus } failureClass := ""