fix(auth): validate and persist shared Nous routing

This commit is contained in:
kshitijk4poor 2026-07-10 13:32:34 +05:30 committed by kshitij
parent 03b8a00e26
commit 0e67c7231d
2 changed files with 113 additions and 4 deletions

View file

@ -5579,15 +5579,24 @@ def resolve_nous_runtime_credentials(
portal_url = env_portal_override.rstrip("/")
else:
parsed_portal_url = urlparse(portal_url)
portal_host = parsed_portal_url.hostname
loopback_http = (
parsed_portal_url.scheme == "http"
and portal_host in {"localhost", "127.0.0.1"}
)
trusted_scheme = (
parsed_portal_url.scheme == "https" or loopback_http
)
if (
parsed_portal_url.hostname
and parsed_portal_url.hostname not in _NOUS_PORTAL_ALLOWED_HOSTS
not portal_host
or portal_host not in _NOUS_PORTAL_ALLOWED_HOSTS
or not trusted_scheme
):
logger.warning(
"auth: ignoring invalid portal_base_url %r "
"(host %r not in allowlist), using default",
"(host %r or scheme not allowed), using default",
portal_url,
parsed_portal_url.hostname,
portal_host,
)
portal_url = DEFAULT_NOUS_PORTAL_URL
@ -5769,6 +5778,11 @@ def resolve_nous_runtime_credentials(
inference_base_url = (
_nous_inference_env_override() or stored_inference_base_url
)
# Persist network-derived routing with rotated tokens so
# a later JWT validation failure cannot leave the profile
# and shared stores on stale metadata. Never persist the
# operator-only env overlay.
state["inference_base_url"] = stored_inference_base_url
state["obtained_at"] = now.isoformat()
state["expires_in"] = access_ttl
state["expires_at"] = datetime.fromtimestamp(

View file

@ -2067,6 +2067,58 @@ def test_runtime_shared_recovery_recomputes_routing_before_force_refresh(
assert profile_state["refresh_token"] == "rotated-shared-refresh"
def test_runtime_unusable_local_token_recomputes_shared_routing(
tmp_path, monkeypatch, shared_store_env,
):
"""The unusable-token merge branch must also adopt shared routing."""
from hermes_cli import auth as auth_mod
profile_b = tmp_path / "profile_b"
_setup_nous_auth(
profile_b,
access_token="local-not-an-invoke-jwt",
refresh_token="local-stale-refresh",
expires_at="2000-01-01T00:00:00+00:00",
expires_in=0,
)
auth_path = profile_b / "auth.json"
auth_payload = json.loads(auth_path.read_text())
local_state = auth_payload["providers"]["nous"]
local_state["portal_base_url"] = "http://127.0.0.1:8001"
local_state["client_id"] = "local-client"
auth_path.write_text(json.dumps(auth_payload, indent=2))
monkeypatch.setenv("HERMES_HOME", str(profile_b))
shared_state = _full_state_fixture()
shared_state["access_token"] = "shared-not-an-invoke-jwt"
shared_state["refresh_token"] = "shared-refresh"
shared_state["expires_at"] = _future_iso(3600)
shared_state["portal_base_url"] = "http://localhost:8002"
shared_state["client_id"] = "shared-client"
auth_mod._write_shared_nous_state(shared_state)
captured = {}
refreshed_token = _invoke_jwt(seconds=7200)
def _refresh(**kwargs):
captured.update(kwargs)
return {
"access_token": refreshed_token,
"refresh_token": "rotated-refresh",
"expires_in": 7200,
"scope": auth_mod.DEFAULT_NOUS_SCOPE,
}
monkeypatch.setattr(auth_mod, "_refresh_access_token", _refresh)
creds = auth_mod.resolve_nous_runtime_credentials()
assert captured["portal_base_url"] == "http://localhost:8002"
assert captured["client_id"] == "shared-client"
assert captured["refresh_token"] == "shared-refresh"
assert creds["api_key"] == refreshed_token
def test_runtime_shared_recovery_honors_inference_env_override(
tmp_path, monkeypatch, shared_store_env,
):
@ -2325,3 +2377,46 @@ class TestStalePortalBaseUrlMigration:
auth_mod.resolve_nous_runtime_credentials()
assert len(refresh_calls) == 1
assert refresh_calls[0] == auth_mod.DEFAULT_NOUS_PORTAL_URL
def test_runtime_credentials_rejects_http_for_production_portal(
self, tmp_path, monkeypatch,
):
"""An allowlisted production host is still unsafe over plain HTTP."""
from hermes_cli import auth as auth_mod
hermes_home = tmp_path / "hermes"
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
_setup_nous_auth(
hermes_home,
access_token=_invoke_jwt(seconds=-60),
refresh_token="valid-refresh",
expires_at=_future_iso(-60),
expires_in=0,
)
auth_file = hermes_home / "auth.json"
store = json.loads(auth_file.read_text())
store["providers"]["nous"]["portal_base_url"] = (
"http://portal.nousresearch.com"
)
auth_file.write_text(json.dumps(store, indent=2))
refresh_calls = []
def _fake_refresh_access_token(
*, client, portal_base_url, client_id, refresh_token,
):
del client, client_id, refresh_token
refresh_calls.append(portal_base_url)
return {
"access_token": _invoke_jwt(seconds=3600),
"refresh_token": "new-refresh",
"expires_in": 3600,
"scope": "inference:invoke",
}
monkeypatch.setattr(
auth_mod, "_refresh_access_token", _fake_refresh_access_token
)
auth_mod.resolve_nous_runtime_credentials()
assert refresh_calls == [auth_mod.DEFAULT_NOUS_PORTAL_URL]