From 07ee457a215b02a65d3914e4c684da40af4e50da Mon Sep 17 00:00:00 2001 From: nftpoetrist <264138787+nftpoetrist@users.noreply.github.com> Date: Tue, 1 Sep 2026 22:23:10 +0300 Subject: [PATCH] fix(wecom): scope WECOM_BOT_ID reads to the active profile under multiplexing WeComAdapter.__init__ read WECOM_BOT_ID via a raw os.getenv() call, while the immediately adjacent line for WECOM_SECRET already used the module's _get_scoped_secret() helper. Under gateway.multiplex_profiles, a secondary profile's adapter is constructed inside a scoped context where os.environ still holds the DEFAULT profile's env-bridge output -- so a secondary profile's bot would silently connect using the default profile's bot_id while (correctly) using its own secret, or vice versa on a scope miss. Switch the bot_id read to _get_scoped_secret(), matching the sibling _secret/_dm_policy/_group_policy/allow_from reads in the same __init__ that were already migrated in #76664/#93545. _standalone_send's out-of-process fallback branch constructs a fresh WeComAdapter(pconfig) and therefore inherits this fix automatically -- no separate change needed there. Adds two regression tests to the existing TestWeComAdapterAuthzScope class (already covering dm_policy/allow_from scoping per #93522), mirroring its established fixture/assertion style. Mutation-verified: both fail against the pre-fix code (asserting the default profile's bot_id leaks into a secondary profile's scope) and pass with the fix. --- plugins/platforms/wecom/adapter.py | 2 +- tests/gateway/test_wecom.py | 32 ++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/plugins/platforms/wecom/adapter.py b/plugins/platforms/wecom/adapter.py index c52af3dae6..1e05a2f761 100644 --- a/plugins/platforms/wecom/adapter.py +++ b/plugins/platforms/wecom/adapter.py @@ -318,7 +318,7 @@ class WeComAdapter(BasePlatformAdapter): super().__init__(config, Platform.WECOM) extra = config.extra or {} - self._bot_id = str(extra.get("bot_id") or os.getenv("WECOM_BOT_ID", "")).strip() + self._bot_id = str(extra.get("bot_id") or _get_scoped_secret("WECOM_BOT_ID", "")).strip() self._secret = str(extra.get("secret") or _get_scoped_secret("WECOM_SECRET", "")).strip() self._ws_url = str( extra.get("websocket_url") diff --git a/tests/gateway/test_wecom.py b/tests/gateway/test_wecom.py index a46a1caded..c167524511 100644 --- a/tests/gateway/test_wecom.py +++ b/tests/gateway/test_wecom.py @@ -76,6 +76,38 @@ class TestWeComAdapterAuthzScope: assert adapter._dm_policy == "pairing" assert adapter._allow_from == [] + def test_scoped_construction_reads_bot_id_from_scope_not_environ(self, multiplex_on, monkeypatch): + """bot_id must honor the same scope as its neighboring _secret read + (both are read on adjacent lines in __init__) -- a secondary profile's + own bot_id must never fall back to the default profile's os.environ + value.""" + from agent import secret_scope + from plugins.platforms.wecom.adapter import WeComAdapter + + monkeypatch.setenv("WECOM_BOT_ID", "default-profile-bot-id") + monkeypatch.setenv("WECOM_SECRET", "default-profile-secret") + token = secret_scope.set_secret_scope( + {"WECOM_BOT_ID": "scoped-bot-id", "WECOM_SECRET": "scoped-secret"} + ) + try: + adapter = WeComAdapter(PlatformConfig(enabled=True)) + finally: + secret_scope.reset_secret_scope(token) + assert adapter._bot_id == "scoped-bot-id" + assert adapter._secret == "scoped-secret" + + def test_scoped_miss_does_not_leak_default_profiles_bot_id(self, multiplex_on, monkeypatch): + from agent import secret_scope + from plugins.platforms.wecom.adapter import WeComAdapter + + monkeypatch.setenv("WECOM_BOT_ID", "default-profile-bot-id") + token = secret_scope.set_secret_scope({"SOMETHING_ELSE": "x"}) + try: + adapter = WeComAdapter(PlatformConfig(enabled=True)) + finally: + secret_scope.reset_secret_scope(token) + assert adapter._bot_id == "" + class TestWeComConnect: