fix(runtime): treat 'ollama'/'vllm'/'llamacpp' aliases like 'custom' for base_url trust (#27132)

When config.yaml has provider: ollama (or vllm/llamacpp/llama-cpp) with a
non-loopback base_url, auth.py's resolve_provider() correctly normalises
the alias to 'custom' at the top level, but two sites in runtime_provider.py
were still comparing the *original* string against the literal 'custom':

  - _config_base_url_trustworthy_for_bare_custom() rejected non-loopback
    URLs because cfg_provider_norm was 'ollama', not 'custom'.
  - _resolve_openrouter_runtime() only entered the trust branch when
    requested_norm == 'custom'.

Both sites now consult resolve_provider() and treat any alias that
resolves to 'custom' identically. Result: provider: ollama + LAN IP no
longer silently falls through to OpenRouter (HTTP 401), matching the
behaviour of provider: custom with the same base_url.

E2E verified across 6 cases (ollama/vllm/llamacpp/custom + LAN; ollama +
loopback; openrouter + cloud) — all route to the configured endpoint;
'frobnicate' + LAN still rejects with AuthError as before.

Also adds scripts/release.py AUTHOR_MAP entry for @stepanov1975
(PR #22074 — wizard config picker preservation, cherry-picked into the
preceding commit).
This commit is contained in:
teknium1 2026-05-19 11:30:25 -07:00 committed by Teknium
parent e13f242f01
commit 6a159be7ca
3 changed files with 111 additions and 1 deletions

View file

@ -47,7 +47,8 @@ def _config_base_url_trustworthy_for_bare_custom(cfg_base_url: str, cfg_provider
"""Decide whether ``model.base_url`` may back bare ``custom`` runtime resolution.
GitHub #14676: the model picker can select Custom while ``model.provider`` still reflects a
previous provider. Reject non-loopback URLs unless the YAML provider is already ``custom``,
previous provider. Reject non-loopback URLs unless the YAML provider is already ``custom``
(or one of the local-server aliases that resolve to ``custom`` ollama, vllm, llamacpp, ),
so a stale OpenRouter/Z.ai base_url cannot hijack local ``custom`` sessions.
"""
cfg_provider_norm = (cfg_provider or "").strip().lower()
@ -56,6 +57,17 @@ def _config_base_url_trustworthy_for_bare_custom(cfg_base_url: str, cfg_provider
return False
if cfg_provider_norm == "custom":
return True
# GitHub #27132: provider aliases that resolve to "custom" at runtime
# (ollama, vllm, llamacpp, …) should be trusted the same way "custom"
# is, otherwise a legit LAN/WireGuard ollama endpoint silently falls
# through to OpenRouter.
try:
from hermes_cli.auth import resolve_provider as _resolve_provider
if _resolve_provider(cfg_provider_norm) == "custom":
return True
except Exception:
pass
if base_url_host_matches(bu, "openrouter.ai"):
return False
return _loopback_hostname(base_url_hostname(bu))
@ -547,7 +559,20 @@ def _resolve_named_custom_runtime(
# Bare `provider="custom"` with an explicit base_url (e.g. propagated
# from a `model_aliases:` direct-alias resolution) — build a runtime
# directly so the alias's base_url actually takes effect.
#
# GitHub #27132: provider aliases that resolve to "custom" at runtime
# (ollama, vllm, llamacpp, …) are treated identically here, so a YAML
# `provider: ollama` with a LAN/WireGuard `base_url` doesn't silently
# fall through to OpenRouter.
requested_norm = (requested_provider or "").strip().lower()
if requested_norm and requested_norm != "custom":
try:
from hermes_cli.auth import resolve_provider as _resolve_provider
if _resolve_provider(requested_norm) == "custom":
requested_norm = "custom"
except Exception:
pass
if requested_norm == "custom" and explicit_base_url:
base_url = explicit_base_url.strip().rstrip("/")
# Check credential pool first — mirrors the named-custom-provider path
@ -638,6 +663,19 @@ def _resolve_openrouter_runtime(
break
requested_norm = (requested_provider or "").strip().lower()
cfg_provider = cfg_provider.strip().lower()
# GitHub #27132: provider aliases that resolve to "custom" (ollama,
# vllm, llamacpp, …) follow the same base_url trust + routing rules
# as a bare `provider: custom`. Normalising here keeps every check
# below — `requested_norm == "custom"`, the trust check, the pool
# gate up the stack — alias-aware without duplicating the alias map.
if requested_norm and requested_norm != "custom":
try:
from hermes_cli.auth import resolve_provider as _resolve_provider
if _resolve_provider(requested_norm) == "custom":
requested_norm = "custom"
except Exception:
pass
env_openrouter_base_url = os.getenv("OPENROUTER_BASE_URL", "").strip()
env_custom_base_url = os.getenv("CUSTOM_BASE_URL", "").strip()

View file

@ -1146,6 +1146,7 @@ AUTHOR_MAP = {
"192385615+LifeJiggy@users.noreply.github.com": "LifeJiggy", # stale salvage commit alias (PR #28315)
"beastant1@gmail.com": "nekwo", # PR #26481 (PS5.1 UTF-8 BOM)
"43717185+nekwo@users.noreply.github.com": "nekwo",
"9785479+stepanov1975@users.noreply.github.com": "stepanov1975", # PR #22074 (setup config picker writes)
"67979730+flooryyyy@users.noreply.github.com": "flooryyyy", # PR #26374 (tool_trace error detection)
"188585318+dgians@users.noreply.github.com": "dgians", # PR #26034 (.ts/.py/.sh docs types)
"zealy@tz.co": "dgians", # PR #26034 (bot-committed by zealy-tzco under dgians' PR)

View file

@ -2321,3 +2321,74 @@ def test_minimax_oauth_pool_forces_anthropic_messages_despite_stale_config(monke
assert resolved["provider"] == "minimax-oauth"
assert resolved["api_mode"] == "anthropic_messages"
assert resolved["base_url"] == "https://api.minimax.io/anthropic"
# ----------------------------------------------------------------------
# GitHub #27132 — provider aliases (ollama/vllm/llamacpp/llama-cpp) must
# follow the same base_url trust + routing rules as bare `provider: custom`.
# Without this, a YAML `provider: ollama` with a LAN/WireGuard `base_url`
# silently falls through to OpenRouter (HTTP 401).
# ----------------------------------------------------------------------
@pytest.mark.parametrize(
"alias,base_url",
[
("ollama", "http://192.168.0.103:11434/v1"),
("vllm", "http://192.168.0.103:8000/v1"),
("llamacpp", "http://192.168.0.103:8080/v1"),
("llama-cpp", "http://192.168.0.103:8080/v1"),
],
)
def test_custom_aliases_with_lan_base_url_route_to_custom_not_openrouter(
monkeypatch, alias, base_url
):
"""provider: ollama|vllm|llamacpp + LAN IP must NOT fall through to OpenRouter."""
monkeypatch.setattr(
rp,
"_get_model_config",
lambda: {"provider": alias, "base_url": base_url},
)
# Pretend OPENROUTER_API_KEY is set so the openrouter fallback would
# otherwise succeed — we want to prove the alias short-circuits before
# reaching it.
monkeypatch.setenv("OPENROUTER_API_KEY", "sk-or-fake-test")
# No custom credential pool — exercise the bare-alias path.
monkeypatch.setattr(rp, "load_pool", lambda provider: None)
resolved = rp.resolve_runtime_provider()
assert resolved["provider"] == "custom", (
f"alias {alias!r} with LAN base_url should resolve to provider=custom, "
f"got {resolved['provider']!r}"
)
assert resolved["base_url"] == base_url.rstrip("/"), (
f"base_url should be the configured LAN endpoint, got {resolved['base_url']!r}"
)
def test_custom_alias_with_loopback_base_url_routes_to_custom(monkeypatch):
"""provider: ollama + loopback should also route to custom (regression guard)."""
monkeypatch.setattr(
rp,
"_get_model_config",
lambda: {"provider": "ollama", "base_url": "http://localhost:11434/v1"},
)
monkeypatch.setenv("OPENROUTER_API_KEY", "sk-or-fake-test")
monkeypatch.setattr(rp, "load_pool", lambda provider: None)
resolved = rp.resolve_runtime_provider()
assert resolved["provider"] == "custom"
assert resolved["base_url"] == "http://localhost:11434/v1"
def test_trustworthy_check_accepts_custom_aliases():
"""_config_base_url_trustworthy_for_bare_custom() must accept aliases for custom."""
fn = rp._config_base_url_trustworthy_for_bare_custom
for alias in ("ollama", "vllm", "llamacpp", "llama-cpp", "llama.cpp"):
assert fn("http://192.168.0.103:11434/v1", alias) is True, (
f"alias {alias!r} should be trusted with non-loopback base_url"
)
# Unrelated provider name should still be rejected with non-loopback URL.
assert fn("http://192.168.0.103:11434/v1", "openrouter") is False