fix(proxy): require OpenAI path segment for shared Azure Cognitive Services domains

Address Greptile review: the `*.cognitiveservices.azure.com` /
`*.openai.azure.com` domains are shared by every Azure Cognitive Service
(Speech, Vision, Language, ...), so a hostname-only substring match
misclassified non-OpenAI Azure traffic as OpenAI routes.

- Replace the substring host test with suffix matching (rejects look-alike
  domains like cognitiveservices.azure.com.attacker.example).
- Add `_is_openai_compatible_url` that requires an OpenAI-style path marker
  (`/openai/` or `/v1/`) on the shared Azure domains, and use it in
  PassThroughEndpointLogging.is_openai_route (previously hostname-only).
- Add negative tests for Azure Speech/Vision paths and look-alike domains.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Sameer Kankute 2026-06-08 19:03:27 +05:30
parent b7f13957ec
commit 5c915f59a5
No known key found for this signature in database
3 changed files with 97 additions and 16 deletions

View file

@ -32,23 +32,70 @@ from litellm.types.passthrough_endpoints.pass_through_endpoints import (
from litellm.types.utils import ImageResponse, LlmProviders, PassthroughCallTypes
from litellm.utils import ModelResponse, TextCompletionResponse
# Hostnames that route to OpenAI-compatible APIs. `cognitiveservices.azure.com`
# is the Azure subdomain used by newer Azure OpenAI resource types (e.g. the
# "Azure AI Foundry" / Cognitive Services-hosted deployments). Older Azure
# OpenAI resources use `openai.azure.com`; both are valid in production.
_OPENAI_COMPATIBLE_HOSTNAMES = (
"api.openai.com",
"openai.azure.com",
"cognitiveservices.azure.com",
)
# Hostnames that route to OpenAI-compatible APIs.
#
# `api.openai.com` is OpenAI proper. The two Azure domains below are *shared by
# every Azure Cognitive Service* (Speech, Vision, Language, ...), not just Azure
# OpenAI: `openai.azure.com` is the classic Azure OpenAI domain, while
# `cognitiveservices.azure.com` is used by newer "Azure AI Foundry" /
# Cognitive Services-hosted Azure OpenAI deployments. Because the hostname alone
# cannot tell Azure OpenAI apart from the other Cognitive Services on those
# domains, requests there must additionally carry an OpenAI-style path segment.
_OPENAI_HOSTNAMES = ("api.openai.com",)
_AZURE_OPENAI_HOSTNAMES = ("openai.azure.com", "cognitiveservices.azure.com")
# Path markers that identify an Azure request as Azure OpenAI rather than Speech
# / Vision / Language / ... `/openai/` is the native Azure OpenAI path prefix;
# `/v1/` is the OpenAI-v1 surface used by LiteLLM's pass-through routing. Other
# Cognitive Services use service-named prefixes and versions like `/v3.1/`,
# `/v1.0/`, so they do not collide with these markers.
_AZURE_OPENAI_PATH_MARKERS = ("/openai/", "/v1/")
def _hostname_matches(hostname: str, suffixes: tuple) -> bool:
"""True if hostname equals one of `suffixes` or is a subdomain of it.
Uses suffix matching (not a bare substring test) so look-alikes such as
`cognitiveservices.azure.com.attacker.example` are not accepted.
"""
return any(
hostname == suffix or hostname.endswith("." + suffix) for suffix in suffixes
)
def _is_openai_compatible_host(hostname: Optional[str]) -> bool:
"""True if the hostname is one of the recognized OpenAI-compatible
surfaces (OpenAI proper or any Azure OpenAI subdomain)."""
"""True if the hostname is OpenAI proper or one of the Azure OpenAI domains.
Hostname-only check, kept for the route-level helpers that additionally
require a specific OpenAI path (e.g. `/v1/chat/completions`). When only the
hostname would otherwise gate dispatch, use `_is_openai_compatible_url` so
non-OpenAI Azure Cognitive Services on the shared domains are excluded.
"""
if not hostname:
return False
return any(host in hostname for host in _OPENAI_COMPATIBLE_HOSTNAMES)
return _hostname_matches(hostname, _OPENAI_HOSTNAMES) or _hostname_matches(
hostname, _AZURE_OPENAI_HOSTNAMES
)
def _is_openai_compatible_url(url_route: Optional[str]) -> bool:
"""True if the URL targets an OpenAI-compatible API surface.
For the shared Azure Cognitive Services domains we additionally require an
OpenAI-style path segment (`/openai/` or `/v1/`) so non-OpenAI Azure services
(Speech, Vision, Language, ...) on the same domain are not misclassified as
OpenAI routes.
"""
if not url_route:
return False
parsed_url = urlparse(url_route)
hostname = parsed_url.hostname
if not hostname:
return False
if _hostname_matches(hostname, _OPENAI_HOSTNAMES):
return True
if _hostname_matches(hostname, _AZURE_OPENAI_HOSTNAMES):
return any(marker in parsed_url.path for marker in _AZURE_OPENAI_PATH_MARKERS)
return False
class OpenAIPassthroughLoggingHandler(BasePassthroughLoggingHandler):

View file

@ -434,15 +434,19 @@ class PassThroughEndpointLogging:
return False
def is_openai_route(self, url_route: str):
"""Check if the URL route is an OpenAI API route."""
"""Check if the URL route is an OpenAI API route.
Uses the URL-aware helper so that non-OpenAI Azure Cognitive Services
(Speech, Vision, Language, ...) sharing the `*.cognitiveservices.azure.com`
/ `*.openai.azure.com` domains are not misclassified as OpenAI routes.
"""
if not url_route:
return False
from .llm_provider_handlers.openai_passthrough_logging_handler import (
_is_openai_compatible_host,
_is_openai_compatible_url,
)
parsed_url = urlparse(url_route)
return _is_openai_compatible_host(parsed_url.hostname)
return _is_openai_compatible_url(url_route)
def is_gemini_route(
self, url_route: str, custom_llm_provider: Optional[str] = None

View file

@ -824,6 +824,14 @@ class TestOpenAIPassthroughIntegration:
== True
)
assert self.handler.is_openai_route("https://api.openai.com/v1/models") == True
# Azure OpenAI on the shared Cognitive Services domain, identified by an
# OpenAI-style path segment.
assert (
self.handler.is_openai_route(
"https://my-resource.cognitiveservices.azure.com/v1/chat/completions"
)
== True
)
# Negative cases
assert (
@ -840,6 +848,28 @@ class TestOpenAIPassthroughIntegration:
self.handler.is_openai_route("https://api.assemblyai.com/v2/transcript")
== False
)
# Non-OpenAI Azure Cognitive Services share the `cognitiveservices.azure.com`
# domain but must NOT be classified as OpenAI routes (no OpenAI path segment).
assert (
self.handler.is_openai_route(
"https://my-resource.cognitiveservices.azure.com/speechtotext/v3.1/recognize"
)
== False
)
assert (
self.handler.is_openai_route(
"https://my-resource.cognitiveservices.azure.com/vision/v3.2/analyze"
)
== False
)
# A look-alike domain that merely contains an OpenAI host as a substring
# must be rejected by the suffix-based hostname match.
assert (
self.handler.is_openai_route(
"https://cognitiveservices.azure.com.attacker.example/v1/chat/completions"
)
== False
)
assert self.handler.is_openai_route("") == False
@patch(