fix(gateway): scope default-listener api_server requests under multiplex

Rebuilt from PR #61283 onto the /p/<profile>/ routing world (7aa21e336):
_profile_scope(None) now enters the DEFAULT profile's runtime scope when
multiplexing is active instead of returning nullcontext(). api_server is
a port-binding platform living on the default profile, so plain requests
(no /p/ prefix) are the primary path — with fail-closed get_secret they
crashed with UnscopedSecretError on the first credential read (#61276).

All three wrapped call sites (chat-completions executor, /v1/runs agent
construction and _run_sync) inherit the fix through the one seam.
Single-profile gateways keep the no-op. Regression tests ported from
the original PR to the _profile_scope seam.

Fixes #61276
This commit is contained in:
giggling-ginger 2026-07-16 06:40:10 -07:00 committed by Teknium
parent fef0b2d600
commit 6ff65c4d20
2 changed files with 101 additions and 1 deletions

View file

@ -1284,8 +1284,27 @@ class APIServerAdapter(BasePlatformAdapter):
@staticmethod
def _profile_scope(profile: Optional[str]):
"""Enter the multiplex profile runtime scope, or a no-op when unset."""
"""Enter the multiplex profile runtime scope, or a no-op when unset.
When no ``/p/<profile>/`` prefix was given AND multiplexing is active,
enter the DEFAULT profile's scope instead of a no-op: api_server is a
port-binding platform that lives on the default profile, and with
multiplex fail-closed ``get_secret`` active, an unscoped agent run
raises ``UnscopedSecretError`` on its first credential read (#61276).
Single-profile gateways keep the no-op ``get_secret`` falls through
to ``os.environ`` there, unchanged.
"""
if not profile:
try:
from agent.secret_scope import is_multiplex_active
if is_multiplex_active():
from gateway.run import _profile_runtime_scope
from hermes_constants import get_hermes_home
return _profile_runtime_scope(get_hermes_home())
except Exception:
pass
return nullcontext()
from gateway.run import _profile_runtime_scope
from hermes_cli.profiles import get_profile_dir

View file

@ -0,0 +1,81 @@
"""Regression for #61276: api_server agent entry under multiplex isolation.
When gateway.multiplex_profiles is on, get_secret fails closed without a
profile secret scope. Requests with a ``/p/<profile>/`` prefix are scoped by
``_profile_scope(profile)``, but plain requests on the default listener used
to get ``nullcontext()`` so agent runs crashed with UnscopedSecretError on
their first credential read (e.g. OPENROUTER_BASE_URL). ``_profile_scope``
now enters the DEFAULT profile's runtime scope when multiplex is active and
no profile was requested.
Adapted from PR #61283 by @giggling-ginger (originally targeting a
pre-``_profile_scope`` helper); no live gateway or network.
"""
from __future__ import annotations
import pytest
from agent import secret_scope as ss
from gateway.config import PlatformConfig
from gateway.platforms.api_server import APIServerAdapter
@pytest.fixture(autouse=True)
def _reset_multiplex():
ss.set_multiplex_active(False)
yield
ss.set_multiplex_active(False)
@pytest.fixture
def adapter():
return APIServerAdapter(PlatformConfig(enabled=True))
class TestProfileScopeDefaultFallback:
def test_noop_when_multiplex_off(self, adapter, monkeypatch):
monkeypatch.setenv("OPENROUTER_BASE_URL", "https://from-environ.example/v1")
with adapter._profile_scope(None):
# Legacy single-profile path: unscoped get_secret reads os.environ.
assert ss.get_secret("OPENROUTER_BASE_URL") == "https://from-environ.example/v1"
assert ss.current_secret_scope() is None
def test_default_scope_installed_under_multiplex(self, adapter, tmp_path, monkeypatch):
"""No /p/ prefix + multiplex active → default profile scope, not nullcontext."""
(tmp_path / ".env").write_text(
"OPENROUTER_BASE_URL=https://openrouter.ai/api/v1\n",
encoding="utf-8",
)
monkeypatch.setattr(
"hermes_constants.get_hermes_home",
lambda: tmp_path,
)
monkeypatch.setenv("OPENROUTER_BASE_URL", "https://leak.example/v1")
ss.set_multiplex_active(True)
with adapter._profile_scope(None):
assert ss.current_secret_scope() is not None
# Profile .env wins; process env must not leak through.
assert ss.get_secret("OPENROUTER_BASE_URL") == "https://openrouter.ai/api/v1"
# Scope torn down; fail-closed behavior restored outside.
assert ss.current_secret_scope() is None
with pytest.raises(ss.UnscopedSecretError):
ss.get_secret("OPENROUTER_BASE_URL")
def test_named_profile_scope_still_wins(self, adapter, tmp_path, monkeypatch):
"""A /p/<profile>/ request keeps resolving that profile's scope."""
profile_home = tmp_path / "profiles" / "worker"
profile_home.mkdir(parents=True)
(profile_home / ".env").write_text(
"OPENROUTER_BASE_URL=https://worker.example/v1\n", encoding="utf-8"
)
monkeypatch.setattr(
"hermes_cli.profiles.get_profile_dir", lambda name: profile_home
)
ss.set_multiplex_active(True)
with adapter._profile_scope("worker"):
assert ss.get_secret("OPENROUTER_BASE_URL") == "https://worker.example/v1"
assert ss.current_secret_scope() is None