From 564e9b90afe788083d4b766a012ff4b84d359d9b Mon Sep 17 00:00:00 2001 From: Victor Kyriazakos Date: Thu, 30 Jul 2026 19:03:32 +0000 Subject: [PATCH] fix(providers): recognize OpenAI data-residency hosts via one shared predicate Pointing openai-api at OpenAI's documented regional hosts (us.api.openai.com / eu.api.openai.com, mandatory for customers with data-residency obligations) silently degraded Hermes because three subsystems tested 'is this OpenAI' with exact-hostname equality against api.openai.com. Adds providers.is_official_openai_host(): canonical host plus dot-suffix subdomains of api.openai.com, hostname-parsed only. Lookalike hosts (api.openai.com.attacker.test) and path spoofs (proxy.test/api.openai.com/v1) stay rejected, preserving the #32243 hardening: a genuine *.api.openai.com subdomain requires control of openai.com DNS. host_mandated_api_mode() now routes through the predicate, so regional hosts mandate codex_responses exactly like the canonical host. --- hermes_cli/providers.py | 25 +++++++- tests/hermes_cli/test_official_openai_host.py | 57 +++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 tests/hermes_cli/test_official_openai_host.py diff --git a/hermes_cli/providers.py b/hermes_cli/providers.py index 8fc2ac3b41de5..57483e9982008 100644 --- a/hermes_cli/providers.py +++ b/hermes_cli/providers.py @@ -578,6 +578,25 @@ def is_routing_aggregator(provider: str) -> bool: return is_aggregator(provider_norm) +def is_official_openai_host(base_url: str) -> bool: + """True when *base_url* points at OpenAI's official API host family. + + Matches the canonical host (``api.openai.com``) and OpenAI's documented + data-residency / regional hosts (``us.api.openai.com``, + ``eu.api.openai.com``, and any future ``.api.openai.com``) — + those serve the same API surface with the same transport requirements + and the same access-scoped ``/v1/models`` listing. + + Hostname-parsed matching only — never substring — so lookalike hosts + (``api.openai.com.attacker.test``) and path-segment spoofs + (``proxy.test/api.openai.com/v1``) are rejected. A genuine + ``*.api.openai.com`` subdomain requires control of openai.com DNS, so + the dot-suffix match does not reopen the #32243 spoofing hole. + """ + hostname = base_url_hostname(base_url) + return hostname == "api.openai.com" or hostname.endswith(".api.openai.com") + + def host_mandated_api_mode(base_url: str = "") -> Optional[str]: """Return the wire protocol a specific endpoint *requires*, or None. @@ -605,7 +624,11 @@ def host_mandated_api_mode(base_url: str = "") -> Optional[str]: return "anthropic_messages" if hostname == "api.anthropic.com" or url_lower.endswith("/anthropic"): return "anthropic_messages" - if hostname == "api.openai.com": + # Official OpenAI host family: canonical + data-residency regional hosts + # (us./eu.api.openai.com) all mandate the Responses API for reasoning + # models with tools. Shared predicate keeps this lane in lockstep with + # catalog filtering and listing authority. + if is_official_openai_host(base_url): return "codex_responses" if hostname.startswith("bedrock-runtime.") and base_url_host_matches(base_url, "amazonaws.com"): return "bedrock_converse" diff --git a/tests/hermes_cli/test_official_openai_host.py b/tests/hermes_cli/test_official_openai_host.py new file mode 100644 index 0000000000000..da0c92b556e8d --- /dev/null +++ b/tests/hermes_cli/test_official_openai_host.py @@ -0,0 +1,57 @@ +"""Security + parity contract for ``is_official_openai_host``. + +One predicate decides "is this endpoint OpenAI's official API surface?" +for every lane that branches on it: transport mandates +(``host_mandated_api_mode``), URL auto-detection in the runtime resolver, +model-catalog filtering, and live-listing authority. OpenAI's documented +data-residency hosts (``us.api.openai.com``, ``eu.api.openai.com``, and any +future ``.api.openai.com``) are the same API surface as the +canonical host and must match; lookalike/spoof hosts must not (#32243). +""" + +from __future__ import annotations + +import pytest + +from hermes_cli.providers import is_official_openai_host + + +class TestOfficialHosts: + @pytest.mark.parametrize( + "url", + [ + "https://api.openai.com/v1", + "https://api.openai.com", + "https://us.api.openai.com/v1", + "https://eu.api.openai.com/v1", + "https://US.api.OpenAI.com/v1", # case-insensitive hostname + "https://in.api.openai.com/v1", # future regional variants + ], + ) + def test_official_hosts_match(self, url): + assert is_official_openai_host(url) is True + + +class TestSpoofRejection: + @pytest.mark.parametrize( + "url", + [ + # Lookalike host suffix: registrable domain is attacker.test. + "https://api.openai.com.attacker.test/v1", + "https://us.api.openai.com.attacker.test/v1", + # Path-segment spoofing: host is proxy.test. + "https://proxy.test/api.openai.com/v1", + "https://proxy.test/us.api.openai.com/v1", + # Prefix tricks that are NOT dot-separated subdomains of + # api.openai.com (fooapi.openai.com is an openai.com host but + # not the official API host family this predicate is scoped to). + "https://evilapi.openai.com.attacker.test/v1", + "https://fooapi.openai.com/v1", + # Unrelated hosts. + "https://openrouter.ai/api/v1", + "https://api.anthropic.com/v1", + "", + ], + ) + def test_spoof_and_unrelated_hosts_rejected(self, url): + assert is_official_openai_host(url) is False