fix(gateway): harden multiplex credential cluster salvage
Follow-ups on top of the cherry-picked cluster commits: - slack: scope-authoritative app-token read — get_secret() with a narrow UnscopedSecretError fallback to os.getenv. Keeps @kohoj's correct semantics (scoped profile can never silently inherit the default profile's Socket Mode app) while fixing the regression where the default-profile startup loop and background reconnect rebuild, which call connect() unscoped under multiplex, would raise and fail-loop. Supersedes the 'or os.getenv' variant from #64461 which reintroduced the cross-profile fallback leak. - test: unscoped-multiplex fallback regression test for connect(). - run.py: convert the last legacy self.adapters.get(source.platform) site (_rename_discord_auto_thread) to _adapter_for_source(source) so profile-routed Discord sources rename threads on the right adapter (from #57417's sweep). - AUTHOR_MAP entry for @aguung.fix/verification-admin-route-recovery
parent
64746b4bd3
commit
01d3268e02
|
|
@ -14090,7 +14090,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
|||
"""Best-effort semantic rename of a newly auto-created Discord thread."""
|
||||
if not await asyncio.to_thread(self._is_discord_auto_thread_lane, source):
|
||||
return
|
||||
adapter = self.adapters.get(source.platform) if getattr(self, "adapters", None) else None
|
||||
adapter = self._adapter_for_source(source) if getattr(self, "adapters", None) else None
|
||||
if adapter is None:
|
||||
return
|
||||
rename_thread = getattr(adapter, "rename_thread", None)
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ ACP_REGISTRY_MANIFEST = REPO_ROOT / "acp_registry" / "agent.json"
|
|||
|
||||
# Auto-extracted from noreply emails + manual overrides
|
||||
AUTHOR_MAP = {
|
||||
"agungsubastian1963@gmail.com": "aguung", # PR #64461 salvage (gateway: multiplex secret_scope for authz/Slack/webhooks)
|
||||
"doogie@spark.local": "SAMBAS123", # PR #64986 salvage (gateway: multiplex primary bot token scope)
|
||||
"emrekoca2003@gmail.com": "kocaemre", # PR #36051 salvage (docs: audit round 3 code/doc reconciliation)
|
||||
"205466933+wesleion@users.noreply.github.com": "wesleion", # PR #36049 salvage (telegram: per-topic free-response allowlist)
|
||||
|
|
|
|||
|
|
@ -330,6 +330,73 @@ class TestAppMentionHandler:
|
|||
assert created_handlers
|
||||
assert created_handlers[0].app_token == "xapp-profile"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_connect_unscoped_multiplex_falls_back_to_env(self):
|
||||
"""Default-profile connect (multiplex active, NO scope installed) must
|
||||
fall back to process env instead of raising UnscopedSecretError —
|
||||
the primary startup loop and background reconnect rebuild both call
|
||||
connect() unscoped (#59739 salvage follow-up)."""
|
||||
config = PlatformConfig(enabled=True, token="xoxb-default")
|
||||
adapter = SlackAdapter(config)
|
||||
|
||||
def _noop_decorator(_matcher):
|
||||
def decorator(fn):
|
||||
return fn
|
||||
|
||||
return decorator
|
||||
|
||||
mock_app = MagicMock()
|
||||
mock_app.event = _noop_decorator
|
||||
mock_app.command = _noop_decorator
|
||||
mock_app.action = _noop_decorator
|
||||
mock_app.client = AsyncMock()
|
||||
|
||||
mock_web_client = AsyncMock()
|
||||
mock_web_client.auth_test = AsyncMock(
|
||||
return_value={
|
||||
"user_id": "U_DEFAULT",
|
||||
"user": "defaultbot",
|
||||
"team_id": "T_DEFAULT",
|
||||
"team": "DefaultTeam",
|
||||
}
|
||||
)
|
||||
|
||||
created_handlers = []
|
||||
|
||||
class FakeSocketModeHandler:
|
||||
def __init__(self, app, app_token, proxy=None):
|
||||
self.app = app
|
||||
self.app_token = app_token
|
||||
self.proxy = proxy
|
||||
self.client = MagicMock(proxy=None)
|
||||
created_handlers.append(self)
|
||||
|
||||
async def start_async(self):
|
||||
return None
|
||||
|
||||
async def close_async(self):
|
||||
return None
|
||||
|
||||
secret_scope.set_multiplex_active(True)
|
||||
try:
|
||||
with (
|
||||
patch.object(_slack_mod, "AsyncApp", return_value=mock_app),
|
||||
patch.object(_slack_mod, "AsyncWebClient", return_value=mock_web_client),
|
||||
patch.object(
|
||||
_slack_mod, "AsyncSocketModeHandler", FakeSocketModeHandler
|
||||
),
|
||||
patch.dict(os.environ, {"SLACK_APP_TOKEN": "xapp-default"}),
|
||||
patch("gateway.status.acquire_scoped_lock", return_value=(True, None)),
|
||||
patch("asyncio.create_task", side_effect=_fake_create_task),
|
||||
):
|
||||
result = await adapter.connect()
|
||||
finally:
|
||||
secret_scope.set_multiplex_active(False)
|
||||
|
||||
assert result is True
|
||||
assert created_handlers
|
||||
assert created_handlers[0].app_token == "xapp-default"
|
||||
|
||||
|
||||
class TestSlackConnectCleanup:
|
||||
"""Regression coverage for failed connect() cleanup."""
|
||||
|
|
|
|||
Loading…
Reference in New Issue