Merge pull request #39361 from BerriAI/litellm_fix_mantle_host_re_anchor

fix(bedrock_mantle): anchor MANTLE_HOST_RE so custom Mantle hosts are honored
This commit is contained in:
Mateo Wang 2026-09-04 13:40:02 -07:00 committed by GitHub
commit 300d335255
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 85 additions and 1 deletions

View file

@ -29,7 +29,7 @@ from litellm.secret_managers.main import get_secret_str
BEDROCK_MANTLE_DEFAULT_REGION: Final = "us-east-1"
# Standard Mantle host: https://bedrock-mantle.<region>.api.aws (group 1 = region).
MANTLE_HOST_RE: Final = re.compile(r"^https?://bedrock-mantle\.([^/.]+)\.api\.aws", re.IGNORECASE)
MANTLE_HOST_RE: Final = re.compile(r"^https?://bedrock-mantle\.([^/.]+)\.api\.aws(?=/|$)", re.IGNORECASE)
def resolve_mantle_bearer_token(api_key: str | None) -> str | None:

View file

@ -68,6 +68,27 @@ def test_explicit_region_and_non_mantle_api_base_are_kept(no_ambient_aws):
assert base_url == vpc_endpoint
@pytest.mark.parametrize(
"lookalike_host",
[
"https://bedrock-mantle.us-east-1.api.aws.internal.example.com",
"https://bedrock-mantle.us-gov-west-1.api.aws-int.example.com",
"https://bedrock-mantle.us-east-1.api.aws:8443",
],
)
def test_lookalike_mantle_host_api_base_is_kept(no_ambient_aws, lookalike_host):
url, base_url = BedrockMantlePassthroughConfig().get_complete_url(
api_base=lookalike_host,
api_key=None,
model="us.openai.gpt-5.6-sol",
endpoint=INVOKE_ENDPOINT,
request_query_params=None,
litellm_params={"api_base": lookalike_host},
)
assert str(url) == f"{lookalike_host}/{INVOKE_ENDPOINT}"
assert base_url == lookalike_host
def test_region_falls_back_to_the_mantle_default_without_any_hint(no_ambient_aws):
url, _ = BedrockMantlePassthroughConfig().get_complete_url(
api_base=None,

View file

@ -26,6 +26,12 @@ from litellm.llms.bedrock_mantle.responses.transformation import (
from litellm.types.router import GenericLiteLLMParams
from litellm.types.utils import LlmProviders
LOOKALIKE_MANTLE_HOSTS = (
"https://bedrock-mantle.us-east-1.api.aws.internal.example.com",
"https://bedrock-mantle.us-gov-west-1.api.aws-int.example.com",
"https://bedrock-mantle.us-east-1.api.aws:8443",
)
class TestBedrockMantleResponsesURL:
def test_url_uses_region_from_env(self, monkeypatch):
@ -1642,6 +1648,23 @@ class TestBedrockMantleResponsesSigV4:
)
assert url == "https://mantle-proxy.internal.example/openai/v1/responses"
@pytest.mark.parametrize("lookalike_host", LOOKALIKE_MANTLE_HOSTS)
def test_lookalike_mantle_host_from_api_base_is_preserved(self, monkeypatch, lookalike_host):
monkeypatch.delenv("BEDROCK_MANTLE_API_BASE", raising=False)
cfg = BedrockMantleResponsesAPIConfig()
url = cfg.get_complete_url(
api_base=f"{lookalike_host}/openai/v1",
litellm_params={"aws_region_name": "us-east-2"},
)
assert url == f"{lookalike_host}/openai/v1/responses"
@pytest.mark.parametrize("lookalike_host", LOOKALIKE_MANTLE_HOSTS)
def test_lookalike_mantle_host_from_env_is_preserved(self, monkeypatch, lookalike_host):
monkeypatch.setenv("BEDROCK_MANTLE_API_BASE", lookalike_host)
cfg = BedrockMantleResponsesAPIConfig()
url = cfg.get_complete_url(api_base=None, litellm_params={})
assert url == f"{lookalike_host}/openai/v1/responses"
def test_caller_authorization_does_not_override_sigv4(self, monkeypatch):
"""Adversarial-review regression: a caller-supplied Authorization header (e.g.
from extra_headers, surviving the relaxed validate_environment) must not clobber

View file

@ -399,6 +399,46 @@ class TestBedrockMantleChatAuth:
assert "/eu-west-1/bedrock/aws4_request" in headers["Authorization"]
assert "/us-west-2/bedrock/aws4_request" not in headers["Authorization"]
@pytest.mark.parametrize(
("region_params", "env", "expected_region"),
[
({"aws_region_name": "us-west-2"}, {}, "us-west-2"),
({}, {"BEDROCK_MANTLE_REGION": "ap-southeast-2"}, "ap-southeast-2"),
],
)
def test_sigv4_scope_ignores_the_region_segment_of_a_lookalike_host(
self, monkeypatch, region_params, env, expected_region
):
from litellm.llms.bedrock.base_aws_llm import BaseAWSLLM
for var in (
"BEDROCK_MANTLE_API_KEY",
"AWS_BEARER_TOKEN_BEDROCK",
"BEDROCK_MANTLE_REGION",
"BEDROCK_MANTLE_API_BASE",
"AWS_REGION",
"AWS_REGION_NAME",
):
monkeypatch.delenv(var, raising=False)
for var, value in env.items():
monkeypatch.setenv(var, value)
cfg = BedrockMantleChatConfig(aws_signer=BaseAWSLLM())
headers, _ = cfg.sign_request(
headers={},
optional_params={
"aws_access_key_id": "AKIAEXAMPLE",
"aws_secret_access_key": "c2VjcmV0LXRlc3Qtc2VjcmV0LXRlc3Qtc2VjcmV0",
**region_params,
},
request_data={"input": "hi"},
api_base="https://bedrock-mantle.eu-west-1.api.aws.internal.example.com/openai/v1/chat/completions",
api_key=None,
)
assert f"/{expected_region}/bedrock/aws4_request" in headers["Authorization"]
assert "/eu-west-1/bedrock/aws4_request" not in headers["Authorization"]
def test_no_bearer_and_no_credentials_raises_value_error(self, monkeypatch):
from unittest.mock import MagicMock