From 4399ff02e0c48724319c396ebf2013c1fc71129d Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Sat, 4 Jul 2026 07:17:05 +0000 Subject: [PATCH] harden #5511: canonicalise active provider for ownership set (latent alias bug) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex 3rd CORE (pre-existing latent bug the providers: scan exposes): the _provider_models_set ownership lookup used the RAW config_provider (e.g. 'z-ai') but _PROVIDER_MODELS is keyed by canonical slug ('zai'), so an aliased active provider got an empty ownership set -> _skip_custom_providers guard-2 silently failed -> another providers..models entry could hijack an active-owned model (verified: active z-ai + glm-5 + providers.openai.models.glm-5 -> openai). Fix: canonicalise config_provider before the _PROVIDER_MODELS lookup (and reuse the canonical slug for the scan's ownership guard). Regression test with an aliased active provider (verified fail-without-fix). This is the root-cause fix for the alias class — the ownership guard now holds for every aliased provider (zai/kilo/etc.), not just the reported site. Co-authored-by: akay64 --- api/config.py | 16 +++++++++++----- tests/test_model_resolver.py | 26 ++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/api/config.py b/api/config.py index 9ec667a2..5c38aa4b 100644 --- a/api/config.py +++ b/api/config.py @@ -2522,14 +2522,20 @@ def resolve_model_provider(model_id: str) -> tuple: ) _default_model = model_cfg.get('default') if isinstance(model_cfg, dict) else None # Owns model if it appears in the static catalog for the configured provider. + # _PROVIDER_MODELS is keyed by CANONICAL slug (e.g. 'zai', not the 'z-ai' + # alias a user may write in config), so canonicalise config_provider before + # the lookup — otherwise an aliased active provider gets an empty ownership + # set and _skip_custom_providers guard-2 silently fails, letting another + # providers..models entry hijack an active-owned model (#5511). + _canon_config_provider = _canonicalise_provider_id(config_provider) if config_provider else "" _provider_models_set: set[str] = set() if ( - config_provider is not None - and config_provider in _PROVIDER_MODELS - and isinstance(_PROVIDER_MODELS[config_provider], list) + _canon_config_provider + and _canon_config_provider in _PROVIDER_MODELS + and isinstance(_PROVIDER_MODELS[_canon_config_provider], list) ): _provider_models_set = { - m.get('id', '') for m in _PROVIDER_MODELS[config_provider] + m.get('id', '') for m in _PROVIDER_MODELS[_canon_config_provider] if isinstance(m, dict) and isinstance(m.get('id'), str) } _skip_custom_providers = ( @@ -2577,7 +2583,7 @@ def resolve_model_provider(model_id: str) -> tuple: # active provider (#5511 gate finding — e.g. active ai-gateway + default # gpt-5 was being pulled to providers.openai.models.gpt-5). In that case # restrict the scan to the active provider's own canonical slug. - _active_slug = _canonicalise_provider_id(config_provider) if config_provider else "" + _active_slug = _canon_config_provider for slug, pdef in providers_cfg.items(): if not isinstance(pdef, dict): continue diff --git a/tests/test_model_resolver.py b/tests/test_model_resolver.py index d24fe96d..6d05dbbc 100644 --- a/tests/test_model_resolver.py +++ b/tests/test_model_resolver.py @@ -845,3 +845,29 @@ def test_providers_scan_active_provider_own_entry_still_matches_5511(): ) assert provider == 'myprov', f"active provider's own entry must match; got {provider!r}" assert base_url == 'https://my.example/v1' + + +def test_providers_scan_ownership_guard_canonicalises_aliased_active_provider_5511(): + """An ALIASED active provider (e.g. 'z-ai' → canonical 'zai') must still be + recognized as owning its catalog models, so another providers..models + entry can't hijack an active-owned model (#5511 latent-bug gate finding — + _provider_models_set was built with the raw alias, missing the canonical + _PROVIDER_MODELS key, so the ownership guard silently failed).""" + import api.config as config + # Pick a real catalog model id owned by the canonical 'zai' provider. + zai_models = config._PROVIDER_MODELS.get('zai') or [] + zai_ids = [m.get('id') for m in zai_models if isinstance(m, dict) and m.get('id')] + if not zai_ids: + import pytest + pytest.skip("no zai catalog models to exercise the alias ownership guard") + owned = zai_ids[0] + model, provider, base_url = _resolve_with_providers( + owned, + {'openai': {'models': [owned]}}, + provider='z-ai', # aliased form the user may write in config + ) + assert config._canonicalise_provider_id('z-ai') == 'zai' + assert provider == 'z-ai', ( + "aliased active provider (z-ai→zai) that owns the model must not be " + f"hijacked by providers.openai.models; got {provider!r}" + )