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.
This commit is contained in:
Michael Suchacz
2026-04-26 21:50:29 +02:00
committed by GitHub
parent e32581dc68
commit 99a83a2702
3 changed files with 88 additions and 6 deletions
@@ -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",
+4 -4
View File
@@ -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
+2 -2
View File
@@ -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=