fix(web): clear stale api-alias credential on provider switch in main-model assignment

c253b0738 added clear_model_endpoint_credentials() to scrub an old endpoint's
inline secret (api_key, the legacy `api` alias, api_mode) when the web UI
switches the main model to a different provider. But _apply_main_model_assignment
gates the key-scrub path on model_cfg["api_key"] being truthy, so when the stale
secret lives only under the legacy `api` alias (no api_key), a provider switch
never clears it — the secret survives in config.yaml.

model.api is a live credential read path (_resolve_openrouter_runtime reads
`for k in ("api_key", "api")`), so the old endpoint's key contaminates a later
custom resolution — the exact harm clear_model_endpoint_credentials documents.
The sibling persistence sites (the gateway model-picker paths and the aux-slot
path) call the helper unconditionally on a non-custom switch and already scrub
`api`; only this caller had the api_key-only gate.

Widen the guard to fire on either field. The same-provider re-pick and
explicit-new-key paths are unchanged. Adds the api-alias case to the assignment
test (it fails without the fix).
This commit is contained in:
ly-wang19 2026-06-20 17:09:52 +08:00 committed by Teknium
parent 977884e6cd
commit 28028cce55
2 changed files with 18 additions and 1 deletions

View file

@ -1346,7 +1346,12 @@ def _apply_main_model_assignment(
if api_key.strip():
model_cfg["api_key"] = api_key.strip()
model_cfg.pop("api", None)
elif model_cfg.get("api_key") and new_provider != prev_provider:
elif (model_cfg.get("api_key") or model_cfg.get("api")) and new_provider != prev_provider:
# A stale endpoint secret can live under the legacy ``api`` alias with
# no ``api_key`` (the resolver still reads ``model.api`` as a key), so
# the switch-clears-the-key path must trigger on either field — else the
# old endpoint's secret survives in config.yaml and contaminates a later
# custom resolution. clear_model_endpoint_credentials scrubs both.
clear_model_endpoint_credentials(model_cfg, clear_api_mode=False)
if new_provider != prev_provider:
clear_model_endpoint_credentials(model_cfg, clear_api_key=False)

View file

@ -3989,6 +3989,18 @@ class TestWebServerEndpoints:
assert "api_key" not in out
assert "api_mode" not in out
# switching providers when the stale secret lives under the legacy
# ``api`` alias only (no api_key) → it must be cleared too. The resolver
# reads ``model.api`` as a key, so leaving it behind keeps a secret in
# config.yaml that contaminates the next custom resolution.
out = _apply_main_model_assignment(
{"provider": "custom", "api": "sk-legacy-stale", "base_url": "http://endpoint-a/v1"},
"openrouter",
"m",
)
assert "api" not in out
assert "api_key" not in out
def test_parse_model_ids_handles_openai_and_bare_shapes(self):
"""Model discovery must tolerate the common /v1/models shapes and
never raise (so a slightly non-standard local endpoint still works)."""