harden #5511: active provider owns its OWN providers: allowlist (complete ownership model)

Codex 5th CORE: the ownership set only covered static _PROVIDER_MODELS, not the
active provider's own config.yaml providers.<active>.models allowlist. So an
active provider defined purely via providers: (no static catalog) could be
hijacked by another slug's overlapping entry earlier in config order. Fix folds
the active provider's own providers: allowlist (list + dict shapes, copilot
excluded) into _provider_models_set — completing the ownership model so the
active provider owns its declared models from EITHER the static catalog OR its
own providers: entry. Regression test (fail-without-fix verified). 40 model_resolver
tests green.

Co-authored-by: akay64 <akay64@users.noreply.github.com>
This commit is contained in:
nesquena-hermes 2026-07-04 07:28:44 +00:00
parent f2604a22bd
commit d27ef5d196
2 changed files with 47 additions and 0 deletions

View file

@ -2541,6 +2541,31 @@ def resolve_model_provider(model_id: str) -> tuple:
m.get('id', '') for m in _PROVIDER_MODELS[_canon_config_provider]
if isinstance(m, dict) and isinstance(m.get('id'), str)
}
# The active provider may be defined ENTIRELY via config.yaml `providers:`
# (no static _PROVIDER_MODELS entry) with its own `models:` allowlist. Fold
# that allowlist into the ownership set too, so the active provider owns its
# own declared models and can't be hijacked by another providers.<slug>
# entry that happens to list the same bare id earlier in config order (#5511).
if _canon_config_provider:
_providers_cfg_own = cfg.get('providers', {})
if isinstance(_providers_cfg_own, dict):
for _slug, _pdef in _providers_cfg_own.items():
if not isinstance(_pdef, dict):
continue
if _canonicalise_provider_id(_slug) != _canon_config_provider:
continue
if _canon_config_provider == "copilot":
continue # copilot.models is a settings map, not an allowlist
_own_models = _pdef.get('models')
if isinstance(_own_models, list):
for _m in _own_models:
_mid = str(_m.get('id') or '') if isinstance(_m, dict) else str(_m or '')
if _mid.strip():
_provider_models_set.add(_mid.strip())
elif isinstance(_own_models, dict):
_provider_models_set.update(
str(k).strip() for k in _own_models if isinstance(k, str) and str(k).strip()
)
_skip_custom_providers = (
_is_explicit_non_custom_provider
and (

View file

@ -897,3 +897,25 @@ def test_providers_scan_ownership_guard_canonicalises_gemini_alias_5511():
"aliased active Gemini provider that owns the model must not be hijacked "
f"by providers.openai.models; got {provider!r}"
)
def test_providers_scan_active_own_providers_entry_owns_over_other_slug_5511():
"""An active provider defined purely via config.yaml `providers:` (no static
catalog entry) owns the models in its OWN providers.<active>.models allowlist,
so another provider's entry listing the same bare id (even earlier in config
order) must NOT hijack it (#5511 gate finding 5)."""
# 'openai' entry lists gpt-x first, but the ACTIVE provider (myprov) also
# declares gpt-x in its own providers.myprov.models — active must win.
model, provider, base_url = _resolve_with_providers(
'gpt-x',
{
'openai': {'models': ['gpt-x']},
'myprov': {'base_url': 'https://my.example/v1', 'models': ['gpt-x']},
},
provider='myprov',
)
assert provider == 'myprov', (
"active provider's own providers: allowlist must own its model over "
f"another slug's overlapping entry; got {provider!r}"
)
assert base_url == 'https://my.example/v1'