From 99a83a27027ef4a2aeafd7df419fc6b862ed2eee Mon Sep 17 00:00:00 2001 From: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> Date: Sun, 26 Apr 2026 21:50:29 +0200 Subject: [PATCH] fix: clean Bedrock headers (#24718) Bedrock chat provider requests can inherit Anthropic public API headers from the process environment, which causes mixed Anthropic and Bedrock auth headers on signed requests. Update the Anthropic SDK fork so its Bedrock middleware strips Anthropic-only headers before signing requests, and keep a chatprovider regression test for the production request shape. > Mux is acting on Mike's behalf. --- .../x/chatd/chatprovider/chatprovider_test.go | 82 +++++++++++++++++++ go.mod | 8 +- go.sum | 4 +- 3 files changed, 88 insertions(+), 6 deletions(-) diff --git a/coderd/x/chatd/chatprovider/chatprovider_test.go b/coderd/x/chatd/chatprovider/chatprovider_test.go index acb0dc9ea1..bb94d3b0c3 100644 --- a/coderd/x/chatd/chatprovider/chatprovider_test.go +++ b/coderd/x/chatd/chatprovider/chatprovider_test.go @@ -2,6 +2,7 @@ package chatprovider_test import ( "encoding/json" + "io" "net/http" "net/http/httptest" "testing" @@ -967,6 +968,87 @@ func TestModelFromConfig_Bedrock(t *testing.T) { }) } +// TestModelFromConfig_BedrockStripsAnthropicHeaders is a regression test +// for a bug where the Anthropic SDK reads ANTHROPIC_API_KEY from the +// process environment and adds X-Api-Key and Anthropic-Version headers to +// every request. On Bedrock, these headers conflict with SigV4 signing and +// cause auth failures. The SDK's Bedrock middleware strips them before +// signing. This test verifies the outgoing request shape with both +// Anthropic and AWS credentials present. +func TestModelFromConfig_BedrockStripsAnthropicHeaders(t *testing.T) { + ctx := testutil.Context(t, testutil.WaitShort) + + t.Setenv("ANTHROPIC_API_KEY", "anthropic-env-key") + t.Setenv("AWS_REGION", "us-east-2") + t.Setenv("AWS_ACCESS_KEY_ID", "test-access-key") + t.Setenv("AWS_SECRET_ACCESS_KEY", "test-secret-key") + t.Setenv("AWS_SESSION_TOKEN", "test-session-token") + + type requestCapture struct { + Authorization string + AnthropicVersion string + XAPIKey string + Body string + ReadError error + } + + requests := make(chan requestCapture, 1) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + body, err := io.ReadAll(r.Body) + + requests <- requestCapture{ + Authorization: r.Header.Get("Authorization"), + AnthropicVersion: r.Header.Get("Anthropic-Version"), + XAPIKey: r.Header.Get("X-Api-Key"), + Body: string(body), + ReadError: err, + } + + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(bedrockNonStreamingResponse()) + })) + defer server.Close() + + model, err := chatprovider.ModelFromConfig( + fantasybedrock.Name, + "anthropic.claude-opus-4-6-v1", + chatprovider.ProviderAPIKeys{ + ByProvider: map[string]string{ + fantasybedrock.Name: "", + }, + BaseURLByProvider: map[string]string{ + fantasybedrock.Name: server.URL, + }, + }, + chatprovider.UserAgent(), + nil, + nil, + ) + require.NoError(t, err) + require.NotNil(t, model) + + _, err = model.Generate(ctx, fantasy.Call{ + Prompt: []fantasy.Message{ + { + Role: fantasy.MessageRoleUser, + Content: []fantasy.MessagePart{ + fantasy.TextPart{Text: "hello"}, + }, + }, + }, + }) + require.NoError(t, err) + + got := testutil.TryReceive(ctx, t, requests) + require.NoError(t, got.ReadError) + require.Empty(t, got.AnthropicVersion) + require.Empty(t, got.XAPIKey) + require.Contains(t, got.Authorization, "AWS4-HMAC-SHA256") + require.NotContains(t, got.Authorization, "anthropic-version") + require.NotContains(t, got.Authorization, "x-api-key") + require.Contains(t, got.Body, `"anthropic_version":"bedrock-2023-05-31"`) +} + func bedrockNonStreamingResponse() map[string]any { return map[string]any{ "id": "msg_01Test", diff --git a/go.mod b/go.mod index c34586470a..c7c4e3552c 100644 --- a/go.mod +++ b/go.mod @@ -89,10 +89,10 @@ replace github.com/spf13/afero => github.com/aslilac/afero v0.0.0-20250403163713 // See: https://github.com/coder/fantasy/commits/f83367a4a205 replace charm.land/fantasy => github.com/coder/fantasy v0.0.0-20260426185602-951a49c681df -// coder/coder uses a fork of charmbracelet's fork of the Anthropic Go SDK with some -// additional performance improvements. -// See: https://github.com/coder/anthropic-sdk-go/commits/a31d7d0e7067 -replace github.com/charmbracelet/anthropic-sdk-go => github.com/coder/anthropic-sdk-go v0.0.0-20260415160422-a31d7d0e7067 +// coder/coder uses a fork of charmbracelet's fork of the Anthropic Go SDK +// with performance improvements and Bedrock header cleanup. +// See: https://github.com/coder/anthropic-sdk-go/commits/3be8e193ec89 +replace github.com/charmbracelet/anthropic-sdk-go => github.com/coder/anthropic-sdk-go v0.0.0-20260424230212-3be8e193ec89 // Replace sdks with our own optimized forks until relevant upstream PRs are merged. // https://github.com/anthropics/anthropic-sdk-go/pull/262 diff --git a/go.sum b/go.sum index 26c747b5fc..35bb75efaf 100644 --- a/go.sum +++ b/go.sum @@ -314,8 +314,8 @@ github.com/coder/agentapi-sdk-go v0.0.0-20250505131810-560d1d88d225 h1:tRIViZ5JR github.com/coder/agentapi-sdk-go v0.0.0-20250505131810-560d1d88d225/go.mod h1:rNLVpYgEVeu1Zk29K64z6Od8RBP9DwqCu9OfCzh8MR4= github.com/coder/aisdk-go v0.0.9 h1:Vzo/k2qwVGLTR10ESDeP2Ecek1SdPfZlEjtTfMveiVo= github.com/coder/aisdk-go v0.0.9/go.mod h1:KF6/Vkono0FJJOtWtveh5j7yfNrSctVTpwgweYWSp5M= -github.com/coder/anthropic-sdk-go v0.0.0-20260415160422-a31d7d0e7067 h1:v1RAkUO21u0QH6UlUueSHMbgFf++BZZW41Rj6LM2eWo= -github.com/coder/anthropic-sdk-go v0.0.0-20260415160422-a31d7d0e7067/go.mod h1:hqlYqR7uPKOKfnNeicUbZp0Ps0GeYFlKYtwh5HGDCx8= +github.com/coder/anthropic-sdk-go v0.0.0-20260424230212-3be8e193ec89 h1:IVJutHfU944mb4D66K7XdPwKMAJrNC9FOq6JB4bveuI= +github.com/coder/anthropic-sdk-go v0.0.0-20260424230212-3be8e193ec89/go.mod h1:hqlYqR7uPKOKfnNeicUbZp0Ps0GeYFlKYtwh5HGDCx8= github.com/coder/boundary v0.8.4-0.20260304164748-566aeea939ab h1:HrlxyTmMQpOHfSKzRU1vf5TxrmV6vL5OiWq+Dvn5qh0= github.com/coder/boundary v0.8.4-0.20260304164748-566aeea939ab/go.mod h1:BhJhyKW/+zZQzaGZ3vn27if2k0Vx5xLXzq7ZCQx5gPk= github.com/coder/bubbletea v1.2.2-0.20241212190825-007a1cdb2c41 h1:SBN/DA63+ZHwuWwPHPYoCZ/KLAjHv5g4h2MS4f2/MTI=