Skip to content

Commit 99a83a2

Browse files
authored
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.
1 parent e32581d commit 99a83a2

3 files changed

Lines changed: 88 additions & 6 deletions

File tree

coderd/x/chatd/chatprovider/chatprovider_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package chatprovider_test
22

33
import (
44
"encoding/json"
5+
"io"
56
"net/http"
67
"net/http/httptest"
78
"testing"
@@ -967,6 +968,87 @@ func TestModelFromConfig_Bedrock(t *testing.T) {
967968
})
968969
}
969970

971+
// TestModelFromConfig_BedrockStripsAnthropicHeaders is a regression test
972+
// for a bug where the Anthropic SDK reads ANTHROPIC_API_KEY from the
973+
// process environment and adds X-Api-Key and Anthropic-Version headers to
974+
// every request. On Bedrock, these headers conflict with SigV4 signing and
975+
// cause auth failures. The SDK's Bedrock middleware strips them before
976+
// signing. This test verifies the outgoing request shape with both
977+
// Anthropic and AWS credentials present.
978+
func TestModelFromConfig_BedrockStripsAnthropicHeaders(t *testing.T) {
979+
ctx := testutil.Context(t, testutil.WaitShort)
980+
981+
t.Setenv("ANTHROPIC_API_KEY", "anthropic-env-key")
982+
t.Setenv("AWS_REGION", "us-east-2")
983+
t.Setenv("AWS_ACCESS_KEY_ID", "test-access-key")
984+
t.Setenv("AWS_SECRET_ACCESS_KEY", "test-secret-key")
985+
t.Setenv("AWS_SESSION_TOKEN", "test-session-token")
986+
987+
type requestCapture struct {
988+
Authorization string
989+
AnthropicVersion string
990+
XAPIKey string
991+
Body string
992+
ReadError error
993+
}
994+
995+
requests := make(chan requestCapture, 1)
996+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
997+
body, err := io.ReadAll(r.Body)
998+
999+
requests <- requestCapture{
1000+
Authorization: r.Header.Get("Authorization"),
1001+
AnthropicVersion: r.Header.Get("Anthropic-Version"),
1002+
XAPIKey: r.Header.Get("X-Api-Key"),
1003+
Body: string(body),
1004+
ReadError: err,
1005+
}
1006+
1007+
w.Header().Set("Content-Type", "application/json")
1008+
_ = json.NewEncoder(w).Encode(bedrockNonStreamingResponse())
1009+
}))
1010+
defer server.Close()
1011+
1012+
model, err := chatprovider.ModelFromConfig(
1013+
fantasybedrock.Name,
1014+
"anthropic.claude-opus-4-6-v1",
1015+
chatprovider.ProviderAPIKeys{
1016+
ByProvider: map[string]string{
1017+
fantasybedrock.Name: "",
1018+
},
1019+
BaseURLByProvider: map[string]string{
1020+
fantasybedrock.Name: server.URL,
1021+
},
1022+
},
1023+
chatprovider.UserAgent(),
1024+
nil,
1025+
nil,
1026+
)
1027+
require.NoError(t, err)
1028+
require.NotNil(t, model)
1029+
1030+
_, err = model.Generate(ctx, fantasy.Call{
1031+
Prompt: []fantasy.Message{
1032+
{
1033+
Role: fantasy.MessageRoleUser,
1034+
Content: []fantasy.MessagePart{
1035+
fantasy.TextPart{Text: "hello"},
1036+
},
1037+
},
1038+
},
1039+
})
1040+
require.NoError(t, err)
1041+
1042+
got := testutil.TryReceive(ctx, t, requests)
1043+
require.NoError(t, got.ReadError)
1044+
require.Empty(t, got.AnthropicVersion)
1045+
require.Empty(t, got.XAPIKey)
1046+
require.Contains(t, got.Authorization, "AWS4-HMAC-SHA256")
1047+
require.NotContains(t, got.Authorization, "anthropic-version")
1048+
require.NotContains(t, got.Authorization, "x-api-key")
1049+
require.Contains(t, got.Body, `"anthropic_version":"bedrock-2023-05-31"`)
1050+
}
1051+
9701052
func bedrockNonStreamingResponse() map[string]any {
9711053
return map[string]any{
9721054
"id": "msg_01Test",

go.mod

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,10 @@ replace github.com/spf13/afero => github.com/aslilac/afero v0.0.0-20250403163713
8989
// See: https://github.com/coder/fantasy/commits/f83367a4a205
9090
replace charm.land/fantasy => github.com/coder/fantasy v0.0.0-20260426185602-951a49c681df
9191

92-
// coder/coder uses a fork of charmbracelet's fork of the Anthropic Go SDK with some
93-
// additional performance improvements.
94-
// See: https://github.com/coder/anthropic-sdk-go/commits/a31d7d0e7067
95-
replace github.com/charmbracelet/anthropic-sdk-go => github.com/coder/anthropic-sdk-go v0.0.0-20260415160422-a31d7d0e7067
92+
// coder/coder uses a fork of charmbracelet's fork of the Anthropic Go SDK
93+
// with performance improvements and Bedrock header cleanup.
94+
// See: https://github.com/coder/anthropic-sdk-go/commits/3be8e193ec89
95+
replace github.com/charmbracelet/anthropic-sdk-go => github.com/coder/anthropic-sdk-go v0.0.0-20260424230212-3be8e193ec89
9696

9797
// Replace sdks with our own optimized forks until relevant upstream PRs are merged.
9898
// https://github.com/anthropics/anthropic-sdk-go/pull/262

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,8 +314,8 @@ github.com/coder/agentapi-sdk-go v0.0.0-20250505131810-560d1d88d225 h1:tRIViZ5JR
314314
github.com/coder/agentapi-sdk-go v0.0.0-20250505131810-560d1d88d225/go.mod h1:rNLVpYgEVeu1Zk29K64z6Od8RBP9DwqCu9OfCzh8MR4=
315315
github.com/coder/aisdk-go v0.0.9 h1:Vzo/k2qwVGLTR10ESDeP2Ecek1SdPfZlEjtTfMveiVo=
316316
github.com/coder/aisdk-go v0.0.9/go.mod h1:KF6/Vkono0FJJOtWtveh5j7yfNrSctVTpwgweYWSp5M=
317-
github.com/coder/anthropic-sdk-go v0.0.0-20260415160422-a31d7d0e7067 h1:v1RAkUO21u0QH6UlUueSHMbgFf++BZZW41Rj6LM2eWo=
318-
github.com/coder/anthropic-sdk-go v0.0.0-20260415160422-a31d7d0e7067/go.mod h1:hqlYqR7uPKOKfnNeicUbZp0Ps0GeYFlKYtwh5HGDCx8=
317+
github.com/coder/anthropic-sdk-go v0.0.0-20260424230212-3be8e193ec89 h1:IVJutHfU944mb4D66K7XdPwKMAJrNC9FOq6JB4bveuI=
318+
github.com/coder/anthropic-sdk-go v0.0.0-20260424230212-3be8e193ec89/go.mod h1:hqlYqR7uPKOKfnNeicUbZp0Ps0GeYFlKYtwh5HGDCx8=
319319
github.com/coder/boundary v0.8.4-0.20260304164748-566aeea939ab h1:HrlxyTmMQpOHfSKzRU1vf5TxrmV6vL5OiWq+Dvn5qh0=
320320
github.com/coder/boundary v0.8.4-0.20260304164748-566aeea939ab/go.mod h1:BhJhyKW/+zZQzaGZ3vn27if2k0Vx5xLXzq7ZCQx5gPk=
321321
github.com/coder/bubbletea v1.2.2-0.20241212190825-007a1cdb2c41 h1:SBN/DA63+ZHwuWwPHPYoCZ/KLAjHv5g4h2MS4f2/MTI=

0 commit comments

Comments
 (0)