From d30e933e33ff2d98076431c44b668e5fbefc6b65 Mon Sep 17 00:00:00 2001 From: "cursor[bot]" <206951365+cursor[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:12:08 -0700 Subject: [PATCH 1/3] ci: wire credential_endpoints tests into the proxy endpoints job (#36187) PR #36166 added tests/test_litellm/proxy/credential_endpoints/test_endpoints.py but no CI job invokes it, so the CI Coverage guard failed on litellm_internal_staging. Add the directory to the proxy-endpoints job's test-path list so pytest actually runs the new tests and the coverage assertion is satisfied. Co-authored-by: Cursor Agent Co-authored-by: Krrish Dholakia --- .github/workflows/test-unit-proxy-endpoints.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test-unit-proxy-endpoints.yml b/.github/workflows/test-unit-proxy-endpoints.yml index 20b89c72440..645996f779d 100644 --- a/.github/workflows/test-unit-proxy-endpoints.yml +++ b/.github/workflows/test-unit-proxy-endpoints.yml @@ -44,6 +44,7 @@ jobs: tests/test_litellm/proxy/vector_store_endpoints tests/test_litellm/proxy/agent_endpoints tests/test_litellm/proxy/a2a + tests/test_litellm/proxy/credential_endpoints tests/test_litellm/proxy/discovery_endpoints tests/test_litellm/proxy/health_endpoints tests/test_litellm/proxy/shutdown From 0cd58a04d75419f94ec372a2e207f1dc2ea2659b Mon Sep 17 00:00:00 2001 From: ryan-crabbe-berri Date: Fri, 7 Aug 2026 11:13:03 -0700 Subject: [PATCH 2/3] docs(keys): document /key/info fields and clarify budget_reset_at is the next reset (#36127) * docs(keys): document /key/info fields and clarify budget_reset_at is the next reset * docs(keys): drop wrong budget window start math, note reset boundary alignment --- .../key_management_endpoints.py | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/management_endpoints/key_management_endpoints.py b/litellm/proxy/management_endpoints/key_management_endpoints.py index a5078c50fc0..f2cb1124fa0 100644 --- a/litellm/proxy/management_endpoints/key_management_endpoints.py +++ b/litellm/proxy/management_endpoints/key_management_endpoints.py @@ -3528,11 +3528,34 @@ async def info_key_fn( ): """ Retrieve information about a key. + Parameters: - key: Optional[str] = Query parameter representing the key in the request - user_api_key_dict: UserAPIKeyAuth = Dependency representing the user's API key + - key: str | None (query parameter) - The key to look up. Accepts the plaintext key or its hash. + Defaults to the key in the Authorization header. + Returns: - Dict containing the key and its associated information + - key: str - The key that was looked up, echoed back as it was passed in + - info: dict - The key's row, minus the hashed token + - key_alias: str | None - User-friendly key alias + - spend: float - Amount spent by the key. When budget_duration is set this covers only the + current budget window, not the key's lifetime + - max_budget: float | None - Max budget for the key, enforced against spend + - budget_duration: str | None - Budget reset period ("30d", "1h", etc.) + - budget_reset_at: datetime | None - When the current budget window ends and spend is next + reset to 0, not when it was last reset. Reset times snap to standard boundaries in the + configured timezone (30d and 1mo land on the 1st of the month, 7d on Monday, 1h on the + hour), so subtracting budget_duration from it does not give the window's start + - model_max_budget: dict - Per-model budgets, e.g. {"gpt-4": {"budget_limit": 0.0005, "time_period": "30d"}} + - model_max_budget_usage: dict | None - Current-window spend per model, present only when + the key has per-model budgets + - models: list - Model_name's the key is allowed to call + - tpm_limit / rpm_limit: int | None - Tokens and requests per minute limits + - metadata: dict - Metadata for the key, e.g. {"team": "core-infra"} + - blocked: bool | None - Whether the key is blocked + - expires: datetime | None - When the key stops authenticating requests + - last_active: datetime | None - When the key was last used + - object_permission: dict | None - Resolved vector store / MCP permissions when the key has + an object_permission_id Example Curl: ``` From bd289c151c12a60edd6b0bb46adb224f15104ce0 Mon Sep 17 00:00:00 2001 From: yucheng-berri Date: Fri, 7 Aug 2026 11:14:20 -0700 Subject: [PATCH 3/3] fix(azure_sentinel): add AZURE_SENTINEL_AUTHORITY_HOST as a Sentinel scoped override (#36165) Making Sentinel follow AZURE_AUTHORITY_HOST is a breaking change for a deployment that sets that variable for Azure OpenAI or the azure_storage callback while keeping a commercial Sentinel workspace. That deployment had no opt-out, because the proxy constructs the logger with no arguments and the authority_host parameter is reachable only from the SDK. Resolve the authority from AZURE_SENTINEL_AUTHORITY_HOST before falling back to AZURE_AUTHORITY_HOST, matching how tenant id, client id and client secret already resolve in this constructor. --- .../azure_sentinel/azure_sentinel.py | 9 +++-- .../integrations/test_azure_sentinel.py | 36 +++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/litellm/integrations/azure_sentinel/azure_sentinel.py b/litellm/integrations/azure_sentinel/azure_sentinel.py index 563f815b582..24328549094 100644 --- a/litellm/integrations/azure_sentinel/azure_sentinel.py +++ b/litellm/integrations/azure_sentinel/azure_sentinel.py @@ -78,8 +78,8 @@ class AzureSentinelLogger(CustomBatchLogger): If not provided, will use AZURE_SENTINEL_AUDIT_STREAM_NAME env var or the standard stream name. authority_host (str, optional): Microsoft Entra authority host that issues the OAuth2 token, e.g. "https://login.microsoftonline.us" for Azure Government. If not provided, will use - AZURE_AUTHORITY_HOST env var or default to the Azure Public Cloud authority. The Azure - Monitor audience is derived from it. + AZURE_SENTINEL_AUTHORITY_HOST or AZURE_AUTHORITY_HOST env vars, or default to the Azure + Public Cloud authority. The Azure Monitor audience is derived from it. """ self.async_httpx_client = get_async_httpx_client(llm_provider=httpxSpecialProvider.LoggingCallback) @@ -95,7 +95,10 @@ class AzureSentinelLogger(CustomBatchLogger): client_secret or os.getenv("AZURE_SENTINEL_CLIENT_SECRET") or os.getenv("AZURE_CLIENT_SECRET") ) resolved_authority_host: Final = self._normalize_authority_host( - authority_host or os.getenv("AZURE_AUTHORITY_HOST") or DEFAULT_AZURE_AUTHORITY_HOST + authority_host + or os.getenv("AZURE_SENTINEL_AUTHORITY_HOST") + or os.getenv("AZURE_AUTHORITY_HOST") + or DEFAULT_AZURE_AUTHORITY_HOST ) if not resolved_dcr_immutable_id: diff --git a/tests/test_litellm/integrations/test_azure_sentinel.py b/tests/test_litellm/integrations/test_azure_sentinel.py index 56662eea633..f48f5cb1784 100644 --- a/tests/test_litellm/integrations/test_azure_sentinel.py +++ b/tests/test_litellm/integrations/test_azure_sentinel.py @@ -313,6 +313,7 @@ def _build_logger(**overrides): @pytest.fixture def _no_authority_host_env(monkeypatch): + monkeypatch.delenv("AZURE_SENTINEL_AUTHORITY_HOST", raising=False) monkeypatch.delenv("AZURE_AUTHORITY_HOST", raising=False) @@ -389,3 +390,38 @@ async def test_azure_sentinel_token_request_uses_sovereign_authority_and_audienc assert len(token_calls) == 1 assert token_calls[0].kwargs["url"] == "https://login.microsoftonline.us/test-tenant-id/oauth2/v2.0/token" assert token_calls[0].kwargs["data"]["scope"] == "https://monitor.azure.us/.default" + + +def test_azure_sentinel_authority_host_prefers_the_sentinel_scoped_env_var(_no_authority_host_env, monkeypatch): + """AZURE_AUTHORITY_HOST is shared with Azure OpenAI and the azure_storage callback, so a deployment + whose Sentinel workspace lives in a different cloud than the rest of its Azure resources needs a + Sentinel-scoped override. This mirrors how tenant, client id and secret already resolve.""" + monkeypatch.setenv("AZURE_AUTHORITY_HOST", "https://login.microsoftonline.com") + monkeypatch.setenv("AZURE_SENTINEL_AUTHORITY_HOST", "https://login.microsoftonline.us") + + logger = _build_logger() + + assert logger.authority_host == "https://login.microsoftonline.us" + assert logger.oauth_scope == "https://monitor.azure.us/.default" + + +def test_azure_sentinel_falls_back_to_the_shared_authority_host(_no_authority_host_env, monkeypatch): + """With no Sentinel-scoped override the shared variable still applies, which is the behavior + shipped in the original fix.""" + monkeypatch.setenv("AZURE_AUTHORITY_HOST", "https://login.microsoftonline.us") + + logger = _build_logger() + + assert logger.authority_host == "https://login.microsoftonline.us" + assert logger.oauth_scope == "https://monitor.azure.us/.default" + + +def test_azure_sentinel_authority_host_argument_outranks_the_scoped_env_var(_no_authority_host_env, monkeypatch): + """An explicit constructor argument is the most specific source and has to win, otherwise a + deployment that exports the scoped variable silently overrides an SDK caller.""" + monkeypatch.setenv("AZURE_SENTINEL_AUTHORITY_HOST", "https://login.microsoftonline.us") + + logger = _build_logger(authority_host="https://login.microsoftonline.com") + + assert logger.authority_host == "https://login.microsoftonline.com" + assert logger.oauth_scope == "https://monitor.azure.com/.default"