From 501050dbc657ab442c4ec06be4d57c3389a3a865 Mon Sep 17 00:00:00 2001 From: Geoffrey R Plymale <106821302+badMade@users.noreply.github.com> Date: Sun, 31 May 2026 18:31:31 -0400 Subject: [PATCH 1/2] fix(tui): preserve empty tool allowlists --- tests/test_tui_gateway_server.py | 34 ++++++++++++++++++++++++++++++++ tui_gateway/server.py | 27 ++++++++++++++----------- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/tests/test_tui_gateway_server.py b/tests/test_tui_gateway_server.py index 7146de537365..bc7fe8062856 100644 --- a/tests/test_tui_gateway_server.py +++ b/tests/test_tui_gateway_server.py @@ -459,6 +459,18 @@ def test_load_enabled_toolsets_honors_builtin_env_if_config_fails(monkeypatch): assert server._load_enabled_toolsets() == ["web"] +def test_load_enabled_toolsets_preserves_empty_config_allowlist(monkeypatch): + monkeypatch.delenv("HERMES_TUI_TOOLSETS", raising=False) + + import hermes_cli.config as config_mod + import hermes_cli.tools_config as tools_config_mod + + monkeypatch.setattr(config_mod, "load_config", lambda: {"platform_toolsets": {"cli": []}}) + monkeypatch.setattr(tools_config_mod, "_get_platform_tools", lambda *a, **kw: set()) + + assert server._load_enabled_toolsets() == [] + + def test_load_enabled_toolsets_all_env_means_all(monkeypatch): monkeypatch.setenv("HERMES_TUI_TOOLSETS", "all") @@ -4580,6 +4592,16 @@ def test_make_agent_handles_null_agent_config(monkeypatch): assert mock_agent.call_args.kwargs["max_iterations"] == 80 +def test_make_agent_preserves_empty_toolset_allowlist(monkeypatch): + _setup_make_agent_mocks(monkeypatch, {}) + monkeypatch.setattr(server, "_load_enabled_toolsets", lambda: []) + + with patch("run_agent.AIAgent") as mock_agent: + server._make_agent("sid1", "key1") + + assert mock_agent.call_args.kwargs["enabled_toolsets"] == [] + + class _FakeAgentForBackground: base_url = None api_key = None @@ -4634,6 +4656,18 @@ def test_background_agent_kwargs_handles_null_agent_config(monkeypatch): assert kwargs["max_iterations"] == 40 +def test_background_agent_kwargs_preserves_empty_toolset_allowlist(monkeypatch): + class AgentWithNoTools(_FakeAgentForBackground): + enabled_toolsets = [] + + monkeypatch.setattr(server, "_load_cfg", lambda: {}) + monkeypatch.setattr(server, "_load_enabled_toolsets", lambda: ["web"]) + + kwargs = server._background_agent_kwargs(AgentWithNoTools(), "task_1") + + assert kwargs["enabled_toolsets"] == [] + + def test_config_show_displays_nested_max_turns(monkeypatch): monkeypatch.setattr( server, diff --git a/tui_gateway/server.py b/tui_gateway/server.py index 7da3d6d84b94..d1df2b76de06 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -1018,7 +1018,7 @@ def _load_enabled_toolsets() -> list[str] | None: ) if fallback_notice is not None: print(fallback_notice, file=sys.stderr, flush=True) - return enabled or None + return enabled except Exception: if fallback_notice is not None: print( @@ -1810,8 +1810,11 @@ def _background_agent_kwargs(agent, task_id: str) -> dict: "acp_args": getattr(agent, "acp_args", None) or None, "model": getattr(agent, "model", None) or _resolve_model(), "max_iterations": _cfg_max_turns(cfg, 25), - "enabled_toolsets": getattr(agent, "enabled_toolsets", None) - or _load_enabled_toolsets(), + "enabled_toolsets": ( + agent.enabled_toolsets + if getattr(agent, "enabled_toolsets", None) is not None + else _load_enabled_toolsets() + ), "quiet_mode": True, "verbose_logging": False, "ephemeral_system_prompt": getattr(agent, "ephemeral_system_prompt", None) @@ -6237,11 +6240,12 @@ def _(rid, params: dict) -> dict: from toolsets import get_all_toolsets, get_toolset_info session = _sessions.get(params.get("session_id", "")) - enabled = ( - set(getattr(session["agent"], "enabled_toolsets", []) or []) + enabled_toolsets = ( + getattr(session["agent"], "enabled_toolsets", None) if session - else set(_load_enabled_toolsets() or []) + else _load_enabled_toolsets() ) + enabled = set(enabled_toolsets or []) items = [] for name in sorted(get_all_toolsets().keys()): @@ -6253,7 +6257,7 @@ def _(rid, params: dict) -> dict: "name": name, "description": info["description"], "tool_count": info["tool_count"], - "enabled": name in enabled if enabled else True, + "enabled": True if enabled_toolsets is None else name in enabled, "tools": info["resolved_tools"], } ) @@ -6377,11 +6381,12 @@ def _(rid, params: dict) -> dict: from toolsets import get_all_toolsets, get_toolset_info session = _sessions.get(params.get("session_id", "")) - enabled = ( - set(getattr(session["agent"], "enabled_toolsets", []) or []) + enabled_toolsets = ( + getattr(session["agent"], "enabled_toolsets", None) if session - else set(_load_enabled_toolsets() or []) + else _load_enabled_toolsets() ) + enabled = set(enabled_toolsets or []) items = [] for name in sorted(get_all_toolsets().keys()): @@ -6393,7 +6398,7 @@ def _(rid, params: dict) -> dict: "name": name, "description": info["description"], "tool_count": info["tool_count"], - "enabled": name in enabled if enabled else True, + "enabled": True if enabled_toolsets is None else name in enabled, } ) return _ok(rid, {"toolsets": items}) From 8bfa5c2fd9638f00ff09c1e1b7d868f625b2fb3c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Jun 2026 01:44:29 +0000 Subject: [PATCH 2/2] fix(gateway): resolve failing auth and webhook regressions --- gateway/platforms/qqbot/adapter.py | 31 ++++++++++++++++++++++++------ gateway/platforms/webhook.py | 4 ++++ gateway/run.py | 1 + 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/gateway/platforms/qqbot/adapter.py b/gateway/platforms/qqbot/adapter.py index 38e58ffc46e9..f4b5f4291a80 100644 --- a/gateway/platforms/qqbot/adapter.py +++ b/gateway/platforms/qqbot/adapter.py @@ -1130,6 +1130,21 @@ async def _handle_c2c_message( if not self._is_dm_allowed(user_openid): return + source = self.build_source( + chat_id=user_openid, + user_id=user_openid, + chat_type="dm", + ) + allow_attachments = True + gateway_runner = getattr(self, "gateway_runner", None) + if ( + gateway_runner is not None + and hasattr(gateway_runner, "_is_user_authorized") + and not gateway_runner._is_user_authorized(source) + ): + logger.debug("[%s] C2C message blocked by ACL: user=%s", self._log_tag, user_openid) + allow_attachments = False + text = content attachments_raw = d.get("attachments") logger.info( @@ -1156,7 +1171,15 @@ async def _handle_c2c_message( ) # Process all attachments uniformly (images, voice, files) - att_result = await self._process_attachments(attachments_raw) + if allow_attachments: + att_result = await self._process_attachments(attachments_raw) + else: + att_result = { + "image_urls": [], + "image_media_types": [], + "voice_transcripts": [], + "attachment_info": "", + } image_urls = att_result["image_urls"] image_media_types = att_result["image_media_types"] voice_transcripts = att_result["voice_transcripts"] @@ -1195,11 +1218,7 @@ async def _handle_c2c_message( self._chat_type_map[user_openid] = "c2c" event = MessageEvent( - source=self.build_source( - chat_id=user_openid, - user_id=user_openid, - chat_type="dm", - ), + source=source, text=text, message_type=self._detect_message_type(image_urls, image_media_types), raw_message=d, diff --git a/gateway/platforms/webhook.py b/gateway/platforms/webhook.py index 83aa93e94cb3..dfbba2e1694f 100644 --- a/gateway/platforms/webhook.py +++ b/gateway/platforms/webhook.py @@ -590,6 +590,10 @@ def _validate_signature( self, request: "web.Request", body: bytes, secret: str ) -> bool: """Validate webhook signature (GitHub, GitLab, generic HMAC-SHA256).""" + if re.fullmatch(r"\$\{[^}]+\}", secret.strip()): + logger.warning("[webhook] Rejecting unresolved placeholder secret") + return False + # GitHub: X-Hub-Signature-256 = sha256= gh_sig = request.headers.get("X-Hub-Signature-256", "") if gh_sig: diff --git a/gateway/run.py b/gateway/run.py index 8c884307c1f4..e0dd0b830542 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -5371,6 +5371,7 @@ def _is_user_authorized(self, source: SessionSource) -> bool: user_id = source.user_id if not user_id: return False + team_id = (source.guild_id or "").strip() if source.platform == Platform.SLACK else "" platform_env_map = { Platform.TELEGRAM: "TELEGRAM_ALLOWED_USERS",