Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 10 additions & 19 deletions litellm/llms/bedrock/rerank/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,6 @@ def _prepare_request(
data: dict,
optional_params: dict,
) -> BedrockPreparedRequest:
try:
from botocore.auth import SigV4Auth
from botocore.awsrequest import AWSRequest
except ImportError:
raise ImportError("Missing boto3 to call bedrock. Run 'pip install boto3'.")
boto3_credentials_info: Final = self._get_boto_credentials_from_optional_params(optional_params, model)

### SET RUNTIME ENDPOINT ###
Expand All @@ -150,24 +145,20 @@ def _prepare_request(
)
proxy_endpoint_url = proxy_endpoint_url.replace("bedrock-runtime", "bedrock-agent-runtime")
proxy_endpoint_url = f"{proxy_endpoint_url}/rerank"
sigv4: Final = SigV4Auth(
boto3_credentials_info.credentials,
"bedrock",
boto3_credentials_info.aws_region_name,
)
# Make POST Request
body: Final = json.dumps(data).encode("utf-8")

body: Final = json.dumps(data).encode("utf-8")
headers = {"Content-Type": "application/json"}
if extra_headers is not None:
headers = {"Content-Type": "application/json", **extra_headers}
request: Final = AWSRequest(method="POST", url=proxy_endpoint_url, data=body, headers=headers)
sigv4.add_auth(request)
if (
extra_headers is not None and "Authorization" in extra_headers
): # prevent sigv4 from overwriting the auth header
request.headers["Authorization"] = extra_headers["Authorization"]
prepped: Final = request.prepare()

prepped: Final = self.get_request_headers(
credentials=boto3_credentials_info.credentials,
aws_region_name=boto3_credentials_info.aws_region_name,
extra_headers=extra_headers,
endpoint_url=proxy_endpoint_url,
data=body,
headers=headers,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bearer path breaks Bedrock rerank auth

High Severity

Routing rerank through get_request_headers also picks up its AWS_BEARER_TOKEN_BEDROCK branch. Bedrock API keys are not valid for Agents Runtime APIs such as /rerank, so when that env var is set the handler sends Bearer auth instead of SigV4 and AWS rejects the call. Previously this path always signed with SigV4.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d80608e. Configure here.


return BedrockPreparedRequest(
endpoint_url=proxy_endpoint_url,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
) # Adds the parent directory to the system path
import litellm
from litellm.llms.bedrock.base_aws_llm import Boto3CredentialsInfo
from litellm.llms.bedrock.rerank.handler import BedrockRerankHandler
from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler

# Mock response for Bedrock rerank
Expand Down Expand Up @@ -408,3 +409,33 @@ def test_bedrock_rerank_extra_headers_and_headers_merge():

except Exception as e:
pytest.fail(f"Failed to merge and forward headers: {str(e)}")


def test_bedrock_rerank_forwarded_headers_excluded_from_sigv4_signature():
"""
A forwarded header like x-forwarded-for can be rewritten between LiteLLM
signing the request and AWS receiving it (e.g. by an intermediate load
balancer), which invalidates the signature if that header was part of
the signed set. It must still reach Bedrock, just unsigned.
"""
handler = BedrockRerankHandler()

prepared_request = handler._prepare_request(
model="cohere.rerank-v3-5:0",
api_base=None,
extra_headers={"x-forwarded-for": "203.0.113.5"},
data={"query": test_query, "documents": test_documents},
optional_params={
"aws_access_key_id": "test-access-key",
"aws_secret_access_key": "test-secret-key",
"aws_region_name": "us-east-1",
},
)

headers = prepared_request["prepped"].headers
signed_headers = headers["Authorization"].split("SignedHeaders=")[1].split(",")[0].split(";")

assert "x-forwarded-for" not in signed_headers, (
f"x-forwarded-for must not be part of the SigV4 signature, got SignedHeaders={signed_headers}"
)
assert headers["x-forwarded-for"] == "203.0.113.5", "forwarded header must still reach Bedrock, unsigned"
Loading