From d836b2bac4447c099475a38c90b5f577a952d4ef Mon Sep 17 00:00:00 2001 From: aaronagent <1115117931@qq.com> Date: Sun, 28 Jun 2026 19:18:32 -0700 Subject: [PATCH] fix(matrix,mattermost): invite auth check + API path traversal guard Two platform-security hardenings: - Matrix: _on_invite now checks the inviter against the existing allow-list (_allowed_user_ids / GATEWAY_ALLOW_ALL_USERS) before auto-joining. Without this any federated Matrix user could invite the bot into arbitrary rooms, exposing its presence and metadata. The message and reaction paths already enforce this allow-list; the invite path bypassed it. - Mattermost: _api_get / _api_post / _api_put reject any path containing '..'. WebSocket-event values (channel_id, post_id, file_id) are interpolated directly into API paths, so a malicious or compromised server could craft traversal payloads to make the bot issue authenticated requests to arbitrary endpoints with its bearer token. The configurable-E2EE-passphrase change from the original PR is dropped: the matrix adapter was rewritten onto mautrix and the passphrase-protected key-export file no longer exists. --- plugins/platforms/matrix/adapter.py | 19 +++++++++++++++++++ plugins/platforms/mattermost/adapter.py | 9 +++++++++ 2 files changed, 28 insertions(+) diff --git a/plugins/platforms/matrix/adapter.py b/plugins/platforms/matrix/adapter.py index 428b6b6afe0..7cc4cd8aec4 100644 --- a/plugins/platforms/matrix/adapter.py +++ b/plugins/platforms/matrix/adapter.py @@ -2892,6 +2892,25 @@ class MatrixAdapter(BasePlatformAdapter): is_direct = bool(getattr(content, "is_direct", False)) inviter = str(getattr(event, "sender", "")) + # Only auto-join when the inviter is authorized. Without this, any + # federated Matrix user could invite the bot into arbitrary rooms, + # exposing its presence and metadata. Mirrors the allow-list gate + # used on the message/reaction paths. + allow_all = os.getenv("GATEWAY_ALLOW_ALL_USERS", "").lower() in { + "true", + "1", + "yes", + } + if not allow_all and not ( + self._allowed_user_ids and inviter in self._allowed_user_ids + ): + logger.warning( + "Matrix: rejecting invite to %s from unauthorized user %s", + room_id, + inviter, + ) + return + logger.info( "Matrix: invited to %s — joining (is_direct=%s)", room_id, diff --git a/plugins/platforms/mattermost/adapter.py b/plugins/platforms/mattermost/adapter.py index 7bdab48bb66..fc2b6fd8645 100644 --- a/plugins/platforms/mattermost/adapter.py +++ b/plugins/platforms/mattermost/adapter.py @@ -117,6 +117,9 @@ class MattermostAdapter(BasePlatformAdapter): async def _api_get(self, path: str) -> Dict[str, Any]: """GET /api/v4/{path}.""" import aiohttp + if ".." in path: + logger.error("MM API path traversal blocked: %s", path) + return {} url = f"{self._base_url}/api/v4/{path.lstrip('/')}" try: async with self._session.get(url, headers=self._headers(), timeout=aiohttp.ClientTimeout(total=30)) as resp: @@ -134,6 +137,9 @@ class MattermostAdapter(BasePlatformAdapter): ) -> Dict[str, Any]: """POST /api/v4/{path} with JSON body.""" import aiohttp + if ".." in path: + logger.error("MM API path traversal blocked: %s", path) + return {} url = f"{self._base_url}/api/v4/{path.lstrip('/')}" self._last_post_status = None self._last_post_error = "" @@ -213,6 +219,9 @@ class MattermostAdapter(BasePlatformAdapter): ) -> Dict[str, Any]: """PUT /api/v4/{path} with JSON body.""" import aiohttp + if ".." in path: + logger.error("MM API path traversal blocked: %s", path) + return {} url = f"{self._base_url}/api/v4/{path.lstrip('/')}" try: async with self._session.put(