From 6742def4d39c9913aef436dc19f09f9a7d5a70cb Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Sun, 5 Jul 2026 00:20:26 +0000 Subject: [PATCH] Redact provider error bodies --- docs/integrations/openai-compatible-chat.md | 2 +- internal/llm/openai_compatible_client.go | 4 ++-- internal/llm/openai_compatible_client_test.go | 10 +++++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/integrations/openai-compatible-chat.md b/docs/integrations/openai-compatible-chat.md index a44c3c0..8e05f85 100644 --- a/docs/integrations/openai-compatible-chat.md +++ b/docs/integrations/openai-compatible-chat.md @@ -177,7 +177,7 @@ Malformed responses return `ErrMalformedResponse`. ## Error Handling - network/request-construction failures: `ErrRequestFailed` -- non-2xx HTTP status: `ErrUnexpectedStatus` (includes status code and trimmed response body snippet) +- non-2xx HTTP status: `ErrUnexpectedStatus` (includes status code; provider response bodies are not included) - malformed response shape/content: `ErrMalformedResponse` ## Unsupported Or Non-Serialized Fields diff --git a/internal/llm/openai_compatible_client.go b/internal/llm/openai_compatible_client.go index 86152ed..257768d 100644 --- a/internal/llm/openai_compatible_client.go +++ b/internal/llm/openai_compatible_client.go @@ -139,8 +139,8 @@ func (c *OpenAICompatibleClient) Generate(ctx context.Context, req domain.Genera defer httpResp.Body.Close() if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 { - body, _ := io.ReadAll(io.LimitReader(httpResp.Body, 4096)) - return nil, fmt.Errorf("%w: status=%d body=%q", ErrUnexpectedStatus, httpResp.StatusCode, strings.TrimSpace(string(body))) + _, _ = io.Copy(io.Discard, io.LimitReader(httpResp.Body, 4096)) + return nil, fmt.Errorf("%w: status=%d", ErrUnexpectedStatus, httpResp.StatusCode) } var wireResp openAIChatResponse diff --git a/internal/llm/openai_compatible_client_test.go b/internal/llm/openai_compatible_client_test.go index 839cf76..a205361 100644 --- a/internal/llm/openai_compatible_client_test.go +++ b/internal/llm/openai_compatible_client_test.go @@ -903,9 +903,10 @@ func TestOpenAICompatibleClientEndpointOverride(t *testing.T) { } func TestOpenAICompatibleClientNon2xxError(t *testing.T) { + const sensitiveBody = `provider-secret-fragment request_payload_details` ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(`{"error":"bad request payload"}`)) + _, _ = w.Write([]byte(`{"error":"` + sensitiveBody + `"}`)) })) defer ts.Close() @@ -923,8 +924,11 @@ func TestOpenAICompatibleClientNon2xxError(t *testing.T) { if !errors.Is(err, ErrUnexpectedStatus) { t.Fatalf("expected ErrUnexpectedStatus, got %v", err) } - if !strings.Contains(err.Error(), "400") || !strings.Contains(err.Error(), "bad request payload") { - t.Fatalf("expected status/body details, got %v", err) + if !strings.Contains(err.Error(), "status=400") { + t.Fatalf("expected status detail, got %v", err) + } + if strings.Contains(err.Error(), sensitiveBody) { + t.Fatalf("expected provider response body to be redacted, got %v", err) } }