fix(a2a): scope multiplex secondary-profile construction, not shared env

A2AAdapter.__init__ / _default_agent_name / _load_served_agents read
A2A_PORT, A2A_AGENT_NAME and A2A_AGENT_DESCRIPTION from raw os.environ,
so a secondary multiplex profile borrowed the default profile's port and
Agent Card identity. Skip the env read when constructed inside a
secondary profile's scope (_profile_scoped(), #98738 pattern) and fall
to config.extra / module defaults instead. The default profile keeps its
unscoped env precedence.

Salvaged from #100382 (tests trimmed to two).
This commit is contained in:
nftpoetrist
2026-09-02 03:33:14 -07:00
committed by Teknium
parent 504dd1f24f
commit 79732c6452
2 changed files with 139 additions and 5 deletions

View File

@@ -74,8 +74,30 @@ def _reply_timeout() -> float:
return 300.0
def _profile_scoped() -> bool:
"""True when running inside a multiplexed secondary profile's scope.
Secondary-profile adapters are constructed inside ``_profile_runtime_scope``
(secret scope installed + multiplex active) — the same discriminator the
Buzz/SimpleX adapters use for this bug class (#98738). The DEFAULT profile
under multiplexing runs unscoped: ``os.environ`` holds its own bridge
output there and keeps its legacy precedence.
"""
try:
from agent.secret_scope import current_secret_scope, is_multiplex_active
return bool(is_multiplex_active() and current_secret_scope() is not None)
except Exception:
return False
def _default_agent_name() -> str:
name = os.getenv("A2A_AGENT_NAME", "").strip()
# Scope-aware: inside a secondary multiplex profile, os.environ holds the
# DEFAULT profile's bridged A2A_AGENT_NAME — borrowing it would brand a
# secondary profile's Agent Card with another profile's identity. There
# is no per-profile config.yaml equivalent yet, so a scoped profile just
# falls through to the hostname-based default below instead.
name = "" if _profile_scoped() else os.getenv("A2A_AGENT_NAME", "").strip()
if name:
return name
try:
@@ -343,7 +365,15 @@ class A2AAdapter(BasePlatformAdapter):
super().__init__(config=config, platform=platform)
extra = getattr(config, "extra", {}) or {}
self.port = int(os.getenv("A2A_PORT") or extra.get("port", _DEFAULT_PORT))
# Scope-aware: a secondary multiplex profile must not borrow the
# default profile's bridged A2A_PORT (mirrors the Buzz/SimpleX fix
# for #98738) — an unconfigured profile falls closed to the module
# default port instead. (advertised_toolsets has the same env-leak
# shape but is left unscoped here — see the "Scope note" in this
# fix's PR description: open PR #98937 is actively rewriting this
# field's None-vs-empty-list semantics.)
_port_env = None if _profile_scoped() else os.getenv("A2A_PORT")
self.port = int(_port_env or extra.get("port", _DEFAULT_PORT))
self.host = security.resolve_bind_host()
self.agent_name = _default_agent_name()
self._advertised_toolsets = [
@@ -502,9 +532,15 @@ class A2AAdapter(BasePlatformAdapter):
raw = cfg.get("a2a_served_agents") or (cfg.get("a2a") or {}).get("served_agents")
agents: dict[str, dict] = {}
default_desc = os.getenv(
"A2A_AGENT_DESCRIPTION",
"Hermes Agent — a general-purpose agent reachable over A2A.",
# Scope-aware for the same reason as port/toolsets above: a secondary
# profile must not inherit the default profile's A2A_AGENT_DESCRIPTION.
default_desc = (
"Hermes Agent — a general-purpose agent reachable over A2A."
if _profile_scoped()
else os.getenv(
"A2A_AGENT_DESCRIPTION",
"Hermes Agent — a general-purpose agent reachable over A2A.",
)
)
agents[""] = {
"slug": "",

View File

@@ -1618,3 +1618,101 @@ print('fake reply')
title = con.execute("SELECT title FROM sessions WHERE id='sess-1'").fetchone()[0]
con.close()
assert title == "a2a-dev-ctx-unsafe-value"
# --------------------------------------------------------------------------
# Multiplex secondary-profile scope (construction-time config leak)
# --------------------------------------------------------------------------
#
# __init__'s port/advertised-toolsets reads and _load_served_agents's
# description default all previously read raw A2A_* env vars unconditionally.
# Under a multiplexed secondary profile, os.environ holds the DEFAULT
# profile's YAML-to-env bridge output — a secondary profile with its own
# (different, or absent) A2A config would silently borrow the default
# profile's port, toolset advertisement, agent name, or Agent Card
# description. Mirrors the Buzz/SimpleX fix for #98738.
_A2A_ENV_VARS = (
"A2A_PORT",
"A2A_AGENT_NAME",
"A2A_ADVERTISED_TOOLSETS",
"A2A_AGENT_DESCRIPTION",
)
@pytest.fixture(autouse=True)
def _clean_a2a_construction_env(monkeypatch):
"""Keep the new multiplex tests hermetic regardless of ambient env."""
for var in _A2A_ENV_VARS:
monkeypatch.delenv(var, raising=False)
yield
@pytest.fixture
def multiplex_scope():
"""Install multiplex + a secondary-profile secret scope; restore after."""
tokens = []
def install(scope=None):
from agent.secret_scope import set_multiplex_active, set_secret_scope
set_multiplex_active(True)
tokens.append(set_secret_scope(scope or {}))
return tokens[-1]
yield install
from agent.secret_scope import reset_secret_scope, set_multiplex_active
for token in reversed(tokens):
reset_secret_scope(token)
set_multiplex_active(False)
@pytest.fixture
def default_profile_env(monkeypatch):
"""The default profile's YAML-to-env bridge output in os.environ."""
monkeypatch.setenv("A2A_PORT", "9111")
monkeypatch.setenv("A2A_AGENT_NAME", "default-profile-agent")
monkeypatch.setenv("A2A_ADVERTISED_TOOLSETS", "default-only-toolset")
monkeypatch.setenv("A2A_AGENT_DESCRIPTION", "Default profile's own agent.")
class TestMultiplexConstructionScope:
def test_secondary_profile_never_borrows_default_profile_env(
self, multiplex_scope, default_profile_env
):
"""The secondary profile's own config is authoritative; keys absent
from it fall to the module defaults, never to the default profile's
bridged A2A_* env values."""
from plugins.platforms.a2a.adapter import A2AAdapter, _DEFAULT_PORT
from gateway.config import PlatformConfig
multiplex_scope()
assert A2AAdapter(PlatformConfig(enabled=True, extra={"port": 9222})).port == 9222
adapter = A2AAdapter(PlatformConfig(enabled=True, extra={}))
assert adapter.port == _DEFAULT_PORT
assert adapter.agent_name != "default-profile-agent"
assert adapter._agents[""]["description"] == (
"Hermes Agent — a general-purpose agent reachable over A2A."
)
def test_default_profile_unscoped_keeps_env_precedence(
self, monkeypatch, default_profile_env
):
"""Multiplex ON but no scope (the DEFAULT profile constructs
unscoped): env is its own bridge output and still wins."""
from agent.secret_scope import set_multiplex_active
from plugins.platforms.a2a.adapter import A2AAdapter
from gateway.config import PlatformConfig
set_multiplex_active(True)
try:
adapter = A2AAdapter(PlatformConfig(enabled=True, extra={}))
finally:
set_multiplex_active(False)
assert adapter.port == 9111
assert adapter.agent_name == "default-profile-agent"
assert adapter._agents[""]["description"] == "Default profile's own agent."