From 9ff9f771377d5ce0a47db8e979ab08d628375d25 Mon Sep 17 00:00:00 2001 From: Kent Date: Fri, 26 Jun 2026 19:03:10 +0800 Subject: [PATCH] test(router): cover s3_output_bucket_name surviving the trusted credential snapshot The field itself landed on staging via 0c5c9c79d7; these are the regression tests from PR #31435 for the retrieval-facing half. (cherry picked from commit a9a322d63f6d4658b1f28d1622335775e94736a4) --- .../test_bedrock_files_transformation.py | 19 +++++- ...st_azure_ad_token_credential_resolution.py | 68 +++++++++++++++++++ 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/tests/test_litellm/llms/bedrock/files/test_bedrock_files_transformation.py b/tests/test_litellm/llms/bedrock/files/test_bedrock_files_transformation.py index e297c92782b..da13f265ee4 100644 --- a/tests/test_litellm/llms/bedrock/files/test_bedrock_files_transformation.py +++ b/tests/test_litellm/llms/bedrock/files/test_bedrock_files_transformation.py @@ -1992,17 +1992,30 @@ class TestBedrockFileContentTransformation: litellm_params=self._litellm_params(), ) - def _trusted(self, **creds) -> dict: + def _trusted(self, **deployment_litellm_params) -> dict: + """Build the trusted snapshot the way the proxy does: deployment + litellm_params funneled through ``CredentialLiteLLMParams`` (the strict + allowlist ``get_deployment_credentials_with_provider`` applies) before + retrieval ever sees them. Injecting a raw ``MappingProxyType`` would + bypass that filter and hide whether a bucket field actually survives + into the snapshot in production.""" from types import MappingProxyType + from litellm.types.router import CredentialLiteLLMParams + + snapshot = CredentialLiteLLMParams(**deployment_litellm_params).model_dump( + exclude_none=True + ) params = self._litellm_params() - params["_litellm_internal_model_credentials"] = MappingProxyType(dict(creds)) + params["_litellm_internal_model_credentials"] = MappingProxyType(snapshot) return params def test_retrieves_from_distinct_output_bucket(self, monkeypatch): """Batch outputs can land in a separate s3_output_bucket_name. Retrieval must validate the file id against the output bucket too, not just the - input bucket, or the very outputs the feature serves are unreachable.""" + input bucket, or the very outputs the feature serves are unreachable. + The snapshot is built through the production credential filter, so this + fails if s3_output_bucket_name is dropped from that allowlist.""" from litellm.llms.bedrock.files.transformation import BedrockFilesConfig monkeypatch.delenv("AWS_S3_BUCKET_NAME", raising=False) diff --git a/tests/test_litellm/test_azure_ad_token_credential_resolution.py b/tests/test_litellm/test_azure_ad_token_credential_resolution.py index 958b236c9b3..f6f47b3f4c4 100644 --- a/tests/test_litellm/test_azure_ad_token_credential_resolution.py +++ b/tests/test_litellm/test_azure_ad_token_credential_resolution.py @@ -143,3 +143,71 @@ class TestRouterCredentialResolution: assert credentials is not None assert credentials.get("api_key") == "sk-static-key" assert "azure_ad_token" not in credentials + + +class TestRouterCredentialResolutionS3OutputBucket: + """Same strict-dump trap as azure_ad_token (#30235), for Bedrock batch + file retrieval (#26335). Bedrock batch outputs land in a per-model + ``s3_output_bucket_name`` when it differs from the input bucket. The + file-content retrieval path validates a file id against the buckets in the + trusted credential snapshot, and that snapshot is built by round-tripping + the deployment's ``litellm_params`` through ``CredentialLiteLLMParams``. If + the field is undeclared it is dropped, so the output bucket never reaches + retrieval and output-bucket file ids are rejected as foreign.""" + + def test_credentials_preserve_s3_output_bucket_name(self): + from litellm import Router + + deployment_id = "bedrock-batch-output-bucket-fixed-uuid" + router = Router( + model_list=[ + { + "model_name": "bedrock-batch", + "litellm_params": { + "model": "bedrock/anthropic.claude-3-sonnet-20240229-v1:0", + "s3_bucket_name": "in-bucket", + "s3_output_bucket_name": "out-bucket", + "aws_region_name": "us-west-2", + }, + "model_info": {"id": deployment_id}, + } + ] + ) + + credentials = router.get_deployment_credentials_with_provider( + model_id=deployment_id + ) + assert credentials is not None + assert credentials.get("s3_output_bucket_name") == "out-bucket", ( + "Router credential resolution dropped s3_output_bucket_name; " + "Bedrock batch file-content retrieval will reject output-bucket " + "file ids as foreign for model-routed deployments (#26335)" + ) + assert credentials.get("s3_bucket_name") == "in-bucket" + + def test_credentials_without_output_bucket_unaffected(self): + """A deployment that configures only the input bucket keeps it and does + not gain a phantom output bucket in the resolved credentials.""" + from litellm import Router + + deployment_id = "bedrock-batch-input-only-fixed-uuid" + router = Router( + model_list=[ + { + "model_name": "bedrock-batch-input-only", + "litellm_params": { + "model": "bedrock/anthropic.claude-3-sonnet-20240229-v1:0", + "s3_bucket_name": "in-bucket", + "aws_region_name": "us-west-2", + }, + "model_info": {"id": deployment_id}, + } + ] + ) + + credentials = router.get_deployment_credentials_with_provider( + model_id=deployment_id + ) + assert credentials is not None + assert credentials.get("s3_bucket_name") == "in-bucket" + assert "s3_output_bucket_name" not in credentials