From ed6f1bdb9d3643d79af11601bc89e18ab8775e31 Mon Sep 17 00:00:00 2001 From: MarkXian Date: Fri, 28 Aug 2026 11:53:09 +0800 Subject: [PATCH 1/2] fix(bedrock): skip AWS credentials for bearer auth --- litellm/llms/bedrock/base_aws_llm.py | 18 +++++---- litellm/llms/bedrock/chat/converse_handler.py | 40 ++++++++++++------- .../chat/test_bedrock_converse_handler.py | 13 +++++- 3 files changed, 49 insertions(+), 22 deletions(-) diff --git a/litellm/llms/bedrock/base_aws_llm.py b/litellm/llms/bedrock/base_aws_llm.py index 4332848e545..147b85edf37 100644 --- a/litellm/llms/bedrock/base_aws_llm.py +++ b/litellm/llms/bedrock/base_aws_llm.py @@ -1424,10 +1424,15 @@ class BaseAWSLLM: aws_bedrock_runtime_endpoint=aws_bedrock_runtime_endpoint, ) + def _get_bedrock_bearer_token(self, api_key: str | None) -> str | None: + if api_key is not None: + return api_key + return get_secret_str("AWS_BEARER_TOKEN_BEDROCK") + @tracer.wrap() def get_request_headers( self, - credentials: Credentials, + credentials: Credentials | None, aws_region_name: str, extra_headers: dict | None, endpoint_url: str, @@ -1436,12 +1441,9 @@ class BaseAWSLLM: api_key: str | None = None, supports_bearer_token: bool = True, ) -> AWSPreparedRequest: - if not supports_bearer_token: - aws_bearer_token: str | None = None - elif api_key is not None: - aws_bearer_token = api_key - else: - aws_bearer_token = get_secret_str("AWS_BEARER_TOKEN_BEDROCK") + aws_bearer_token: Final = ( + self._get_bedrock_bearer_token(api_key) if supports_bearer_token else None + ) if aws_bearer_token: try: @@ -1456,6 +1458,8 @@ class BaseAWSLLM: from botocore.awsrequest import AWSRequest except ImportError: raise ImportError("Missing boto3 to call bedrock. Run 'pip install boto3'.") + if credentials is None: + raise ValueError("AWS credentials are required when Bedrock bearer token authentication is not configured.") # Filter headers for AWS signature calculation # AWS SigV4 only includes specific headers in signature calculation diff --git a/litellm/llms/bedrock/chat/converse_handler.py b/litellm/llms/bedrock/chat/converse_handler.py index ca5f1298360..ce8347e9594 100644 --- a/litellm/llms/bedrock/chat/converse_handler.py +++ b/litellm/llms/bedrock/chat/converse_handler.py @@ -331,17 +331,22 @@ class BedrockConverseLLM(BaseAWSLLM): litellm_params["aws_region_name"] = aws_region_name # [DO NOT DELETE] important for async calls - credentials: Final[Credentials] = self.get_credentials( - aws_access_key_id=aws_access_key_id, - aws_secret_access_key=aws_secret_access_key, - aws_session_token=aws_session_token, - aws_region_name=aws_region_name, - aws_session_name=aws_session_name, - aws_profile_name=aws_profile_name, - aws_role_name=aws_role_name, - aws_web_identity_token=aws_web_identity_token, - aws_sts_endpoint=aws_sts_endpoint, - aws_external_id=aws_external_id, + aws_bearer_token: Final = self._get_bedrock_bearer_token(api_key) + credentials: Final[Credentials | None] = ( + None + if aws_bearer_token + else self.get_credentials( + aws_access_key_id=aws_access_key_id, + aws_secret_access_key=aws_secret_access_key, + aws_session_token=aws_session_token, + aws_region_name=aws_region_name, + aws_session_name=aws_session_name, + aws_profile_name=aws_profile_name, + aws_role_name=aws_role_name, + aws_web_identity_token=aws_web_identity_token, + aws_sts_endpoint=aws_sts_endpoint, + aws_external_id=aws_external_id, + ) ) ### SET RUNTIME ENDPOINT ### @@ -369,14 +374,21 @@ class BedrockConverseLLM(BaseAWSLLM): # before transforming so whichever path runs emits pre_call once, and # hand down the credentials, region and endpoint this handler already # resolved so both paths sign as the same principal. + credential_params: Final = ( + () + if credentials is None + else ( + ("aws_access_key_id", credentials.access_key), + ("aws_secret_access_key", credentials.secret_key), + ("aws_session_token", credentials.token), + ) + ) rust_optional_params: Final = { # mutable-ok: json.dumps in the bridge rejects a mappingproxy **optional_params, **{ # mutable-ok: merged into its mutable parent above key: value for key, value in ( - ("aws_access_key_id", credentials.access_key), - ("aws_secret_access_key", credentials.secret_key), - ("aws_session_token", credentials.token), + *credential_params, ("aws_region_name", aws_region_name), ) if value is not None diff --git a/tests/test_litellm/llms/bedrock/chat/test_bedrock_converse_handler.py b/tests/test_litellm/llms/bedrock/chat/test_bedrock_converse_handler.py index 8e67a7e3438..4f1610110c3 100644 --- a/tests/test_litellm/llms/bedrock/chat/test_bedrock_converse_handler.py +++ b/tests/test_litellm/llms/bedrock/chat/test_bedrock_converse_handler.py @@ -10,8 +10,8 @@ from unittest.mock import MagicMock, patch import httpx import pytest - from botocore.credentials import Credentials + from litellm.llms.bedrock.chat.converse_handler import BedrockConverseLLM from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler from litellm.rust_bridge import chat_completions as bridge @@ -135,6 +135,17 @@ def test_the_core_receives_the_credentials_this_handler_already_resolved(): assert params["aws_region_name"] == "us-east-1" +def test_bearer_token_auth_does_not_resolve_aws_credentials(): + seen = _inject() + with patch.object(BedrockConverseLLM, "get_credentials") as get_credentials: + BedrockConverseLLM().completion(**_completion_kwargs(api_key="bedrock-bearer-token")) + + get_credentials.assert_not_called() + params = seen["call"][0]["optional_params"] + assert "aws_access_key_id" not in params + assert "aws_secret_access_key" not in params + + def test_the_core_receives_the_converse_url_this_handler_already_built(): seen = _inject() _run() From 0fc61404de49cd140e474d55c2cea092c17ef72e Mon Sep 17 00:00:00 2001 From: MarkXian Date: Fri, 28 Aug 2026 12:08:54 +0800 Subject: [PATCH 2/2] chore: rerun CI against staging base