Skip to content

fix(qqbot): Batch-2 salvage — SSRF allowlist, voice preflight, group auth, WS reconnect wait - #11567

Merged
teknium1 merged 6 commits into
mainfrom
hermes/hermes-7078c790
Apr 17, 2026
Merged

teknium1 merged 6 commits into
mainfrom
hermes/hermes-7078c790

Conversation

@teknium1

Copy link
Copy Markdown
Collaborator

Summary

Batch-2 QQBot salvage — four independent contributor fixes landed together, each with preserved per-commit authorship. Will rebase-merge to keep individual attribution.

Included PRs

◆ #11388 @yeyitech — fix: allow trusted QQ CDN benchmark IP resolution
Current main's tools/url_safety.py has no hostname allowlist, so when a local proxy remaps QQ's CDN (multimedia.nt.qq.com.cn) into benchmark IP space (198.18.0.0/15), SSRF protection blocks all QQ image/voice downloads. PR adds a narrow allowlist: exact hostname match + HTTPS required. Tests cover subdomain rejection, HTTP rejection, DNS-failure rejection.
Closes #11284.

◆ #11294 @plgonzalezrx8 — Fix QQ voice attachment SSRF validation
_stt_voice_attachment() in gateway/platforms/qqbot.py downloaded attacker-supplied URLs with no SSRF check. _download_and_cache() was already guarded — this brings STT in line. Also attaches the shared _ssrf_redirect_guard event hook to the httpx client so redirect chains are validated too.

◆ #10316 @dieutx — fix(gateway): honor QQ_GROUP_ALLOWED_USERS in runner auth
Root cause: _is_user_authorized() mapped Platform.QQBOT → QQ_ALLOWED_USERS only, checked against source.user_id. QQ group allowlisting is keyed by source.chat_id (group openid), so QQ_GROUP_ALLOWED_USERS was never consulted — group messages that passed QQAdapter._is_group_allowed() got rejected at runner auth. PR adds a platform_group_env_map, checks chat_type == "group", authorizes by chat_id. Includes AUTHOR_MAP entry.

◆ #11164 @LehaoLin — fix(gateway): wait for reconnection before dropping WebSocket sends
When QQBot's WebSocket briefly disconnects, send() and _send_media() previously returned immediately with a non-retryable failure — messages silently dropped during the ~1s reconnect window. PR adds _wait_for_reconnection() polling (15s cap, 500ms interval) and marks timeout failures as retryable=True so base _send_with_retry can handle it.
Closes #11163.

Verification

  • All 5 cherry-picks applied cleanly on current origin/main (eabe14a)
  • py_compile OK on all 7 touched files
  • Regression guard validation — temporarily reverted each fix and confirmed its regression test(s) fail cleanly (exact error matches intent); restored and confirmed passing. All 4 PRs' tests genuinely guard their intended behavior.
  • Full targeted suite: 270 passed across test_url_safety, test_qqbot, test_unauthorized_dm_behavior, test_weixin, test_platform_base, test_send_image_file

AUTHOR_MAP additions

Not included in this batch

#10257 (@Quon — qqbot media upload in send_message tool): flagged for separate review. The PR has a no-op pass in the media-only guard that falls through to the error return instead of skipping it, and the follow-up commit 583757cb bundles a platform-guard regression alongside the msg_id fix. The Authorization header typo (QQBotAccessToken → QQBot) IS a real live bug and will be landed as a minimal follow-up after this batch.

On merge

Will close #11388, #11294, #10316, #11164 with credit pointing to this PR.

yeyitech and others added 6 commits April 17, 2026 04:19
When a WebSocket-based platform adapter (e.g. QQ Bot) temporarily
loses its connection, send() now polls is_connected for up to 15s
instead of immediately returning a non-retryable failure. If the
auto-reconnect completes within the window, the message is delivered
normally. On timeout, the SendResult is marked retryable=True so the
base class retry mechanism can attempt re-delivery.

Same treatment applied to _send_media().

Adds 4 async tests covering:
- Successful send after simulated reconnection
- Retryable failure on timeout
- Immediate success when already connected
- _send_media reconnection wait

Fixes #11163
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

5 participants