From 0ddb7589940895b31a9d6eebf7f4e3b3003a9bb3 Mon Sep 17 00:00:00 2001 From: Steven Zimmerman <15812269+EffortlessSteven@users.noreply.github.com> Date: Wed, 3 Jun 2026 21:27:29 -0400 Subject: [PATCH] fix(bedrock): tolerate a missing Connection header when signing get_auth_headers stripped the Connection header from the signed set via del headers["connection"], which raises KeyError on httpx.Headers when the header is absent (e.g. a custom http_client that omits or strips it). Filter the header out instead, matching the robust form already used in lib/aws/_auth.py. Adds unit tests for the bedrock signing helper, covering the missing-header regression and that a present Connection header stays out of SignedHeaders. --- src/anthropic/lib/bedrock/_auth.py | 6 +-- tests/lib/test_bedrock_auth.py | 64 ++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 tests/lib/test_bedrock_auth.py diff --git a/src/anthropic/lib/bedrock/_auth.py b/src/anthropic/lib/bedrock/_auth.py index 0a8b2109d..5669a4e3a 100644 --- a/src/anthropic/lib/bedrock/_auth.py +++ b/src/anthropic/lib/bedrock/_auth.py @@ -56,10 +56,10 @@ def get_auth_headers( # The connection header may be stripped by a proxy somewhere, so the receiver # of this message may not see this header, so we remove it from the set of headers # that are signed. - headers = headers.copy() - del headers["connection"] + new_headers = headers.copy() + new_headers.pop("connection", None) - request = AWSRequest(method=method.upper(), url=url, headers=headers, data=data) + request = AWSRequest(method=method.upper(), url=url, headers=new_headers, data=data) credentials = session.get_credentials() if not credentials: raise RuntimeError("could not resolve credentials from session") diff --git a/tests/lib/test_bedrock_auth.py b/tests/lib/test_bedrock_auth.py new file mode 100644 index 000000000..c3130c415 --- /dev/null +++ b/tests/lib/test_bedrock_auth.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +import re + +import httpx + +from anthropic.lib.bedrock._auth import get_auth_headers + +_ACCESS_KEY = "AKIAIOSFODNN7EXAMPLE" +_SECRET_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" + + +class TestGetAuthHeaders: + def test_returns_authorization_and_date_headers(self) -> None: + headers = get_auth_headers( + method="POST", + url="https://bedrock-runtime.us-east-1.amazonaws.com/model/m/invoke", + headers=httpx.Headers({"content-type": "application/json"}), + aws_access_key=_ACCESS_KEY, + aws_secret_key=_SECRET_KEY, + aws_session_token=None, + region="us-east-1", + profile=None, + data='{"hello": "world"}', + ) + assert headers["Authorization"].startswith("AWS4-HMAC-SHA256") + assert "X-Amz-Date" in headers + + def test_strips_connection_header_from_signing(self) -> None: + """A present connection header must not be part of the signed set.""" + headers = get_auth_headers( + method="POST", + url="https://bedrock-runtime.us-east-1.amazonaws.com/model/m/invoke", + headers=httpx.Headers({"content-type": "application/json", "Connection": "keep-alive"}), + aws_access_key=_ACCESS_KEY, + aws_secret_key=_SECRET_KEY, + aws_session_token=None, + region="us-east-1", + profile=None, + data='{"hello": "world"}', + ) + signed = re.search(r"SignedHeaders=([^,]+)", headers["Authorization"]) + assert signed is not None + assert "connection" not in signed.group(1).split(";") + + def test_missing_connection_header_does_not_raise(self) -> None: + """Regression: signing must not require a connection header to be present. + + A custom ``http_client`` may omit or strip ``Connection`` before the + request reaches signing; the previous ``del headers["connection"]`` raised + ``KeyError`` in that case. + """ + headers = get_auth_headers( + method="POST", + url="https://bedrock-runtime.us-east-1.amazonaws.com/model/m/invoke", + headers=httpx.Headers({"content-type": "application/json"}), + aws_access_key=_ACCESS_KEY, + aws_secret_key=_SECRET_KEY, + aws_session_token=None, + region="us-east-1", + profile=None, + data='{"hello": "world"}', + ) + assert "Authorization" in headers