fix(anthropic_wif): treat blank identity-source fields as unset

This commit is contained in:
mateo-berri 2026-09-04 22:53:44 -07:00
parent 216da96882
commit 82afbbdba1
3 changed files with 54 additions and 2 deletions

View file

@ -160,7 +160,7 @@ def _resolve_identity_source(
legacy_ref: Final = _resolve_assertion_ref(litellm_params)
return (legacy_ref, None) if legacy_ref is not None else None
params: Final[Mapping[str, object]] = MappingProxyType(
{key: value for key, value in (litellm_params or _EMPTY_PARAMS).items() if value is not None}
{key: value for key, value in (litellm_params or _EMPTY_PARAMS).items() if _is_set(value)}
)
match source_kind:
case AnthropicIdentitySourceKind.internal_issuer.value:
@ -248,7 +248,7 @@ def _build_variant(
field_map: Mapping[str, str],
) -> _IdentitySourceVariant:
fields: Final = MappingProxyType(
{field_map[key]: value for key, value in litellm_params.items() if key in field_map}
{field_map[key]: value for key, value in litellm_params.items() if key in field_map and _is_set(value)}
)
try:
return model.model_validate(fields)
@ -412,6 +412,10 @@ def _config_value(litellm_params: Mapping[str, object] | None, param_key: str, e
return _param_str(litellm_params, param_key) or _env_str(env_name)
def _is_set(value: object) -> bool:
return value is not None and value != ""
def _param_str(litellm_params: Mapping[str, object] | None, key: str) -> str | None:
if litellm_params is None:
return None

View file

@ -1084,6 +1084,28 @@ class TestIdentitySourceValidationFailsClosed:
}
)
def test_blank_optional_and_foreign_fields_count_as_unset(self):
configured = {
"anthropic_federation_rule_id": "fdrl_1",
"anthropic_organization_id": "org-1",
"anthropic_identity_source": "internal_issuer",
"anthropic_issuer_url": "https://issuer.internal.example",
"anthropic_issuer_subject": "workload-a",
"anthropic_issuer_signing_key_ref": ISSUER_SIGNING_KEY_REF,
}
with_blanks = {
**configured,
"anthropic_issuer_audience": "",
"anthropic_issuer_ttl_seconds": "",
"anthropic_keycloak_client_id": "",
}
expected = resolve_anthropic_wif_params(configured)
actual = resolve_anthropic_wif_params(with_blanks)
assert expected is not None and actual is not None
assert actual.assertion_ref == expected.assertion_ref
def test_secret_pasted_into_wrong_field_never_appears_in_the_error(self):
secret_value = "super-secret-client-value-xyz"
with pytest.raises(litellm.AuthenticationError) as exc_info:

View file

@ -361,6 +361,32 @@ class TestCredentialJwksExport:
assert "JWKS_TEST_SIGNING_KEY" not in response.text
assert "PRIVATE KEY" not in response.text
def test_jwks_export_treats_blank_optional_fields_as_unset(self, restore_credential_list, monkeypatch):
monkeypatch.setenv("JWKS_TEST_SIGNING_KEY", _generate_es256_pem())
monkeypatch.setattr(
litellm,
"credential_list",
[
CredentialItem(
credential_name="anthropic-issuer-blanks",
credential_values={
"anthropic_identity_source": "internal_issuer",
"anthropic_issuer_url": "https://issuer.example.com",
"anthropic_issuer_subject": "my-workload",
"anthropic_issuer_signing_key_ref": "os.environ/JWKS_TEST_SIGNING_KEY",
"anthropic_issuer_audience": "",
"anthropic_issuer_ttl_seconds": "",
},
credential_info={"custom_llm_provider": "anthropic"},
)
],
)
response = _get_jwks("anthropic-issuer-blanks")
assert response.status_code == 200, response.text
assert response.json()["keys"][0]["kty"] == "EC"
def test_jwks_export_accepts_the_dashboard_provider_casing(self, restore_credential_list, monkeypatch):
monkeypatch.setenv("JWKS_TEST_SIGNING_KEY", _generate_es256_pem())
monkeypatch.setattr(