fix(wechat): validate timing configuration (#4280)

This commit is contained in:
Ryker_Feng 2026-07-18 17:53:30 +08:00 committed by GitHub
parent 5994fdf38c
commit f8bef42a04
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 40 additions and 6 deletions

View File

@ -427,7 +427,10 @@ channels:
ilink_bot_id: $WECHAT_ILINK_BOT_ID
qrcode_login_enabled: true # optional: allow first-time QR bootstrap when bot_token is absent
allowed_users: [] # empty = allow all
polling_timeout: 35
polling_timeout: 35 # timing values must be positive finite seconds
polling_retry_delay: 5
qrcode_poll_interval: 2
qrcode_poll_timeout: 180
state_dir: ./.deer-flow/wechat/state
max_inbound_image_bytes: 20971520
max_outbound_image_bytes: 20971520

View File

@ -546,6 +546,7 @@ The cached value is reused for both the blocking (`runs.wait`) and streaming (`_
- Disabled by default. It is a user-binding layer on top of the existing `channels.*` runtime config, not a replacement for provider bot credentials.
- No public IP, OAuth callback URL, or provider webhook route is required by the current implementation.
- Telegram uses a deep-link `/start <code>` flow over the existing long-polling worker. Slack, Discord, Feishu/Lark, DingTalk, WeChat, and WeCom use `/connect <code>` over their existing outbound channel workers.
- WeChat timing settings (`polling_timeout`, `polling_retry_delay`, `qrcode_poll_interval`, `qrcode_poll_timeout`) accept only positive finite seconds; invalid values fall back to their defaults so polling cannot enter a hot loop or sleep forever.
- Frontend APIs: `GET /api/channels/providers`, `GET /api/channels/connections`, `POST /api/channels/{provider}/connect`, and `DELETE /api/channels/connections/{connection_id}`.
- Browser APIs remain protected by normal Gateway auth/CSRF. Provider messages arrive through the already-configured channel workers.
- Provider-level `connection_status` reflects the user's newest connection row. With no binding it is `not_connected`, except in auth-disabled local mode where a configured running channel reports `connected` because all channel messages already route to the default user.

View File

@ -8,6 +8,7 @@ import binascii
import hashlib
import json
import logging
import math
import mimetypes
import secrets
import tempfile
@ -1456,9 +1457,10 @@ class WechatChannel(Channel):
@staticmethod
def _coerce_float(value: Any, default: float) -> float:
try:
return float(value)
except (TypeError, ValueError):
parsed = float(value)
except (OverflowError, TypeError, ValueError):
return default
return parsed if math.isfinite(parsed) and parsed > 0 else default
@staticmethod
def _coerce_int(value: Any, default: int) -> int:

View File

@ -84,6 +84,33 @@ class _MockAsyncClient:
return None
def test_timing_config_requires_positive_finite_values():
from app.channels.wechat import WechatChannel
timing_defaults = {
"polling_timeout": WechatChannel.DEFAULT_POLLING_TIMEOUT,
"polling_retry_delay": WechatChannel.DEFAULT_RETRY_DELAY,
"qrcode_poll_interval": WechatChannel.DEFAULT_QRCODE_POLL_INTERVAL,
"qrcode_poll_timeout": WechatChannel.DEFAULT_QRCODE_POLL_TIMEOUT,
}
attributes = {
"polling_timeout": "_polling_timeout",
"polling_retry_delay": "_retry_delay",
"qrcode_poll_interval": "_qrcode_poll_interval",
"qrcode_poll_timeout": "_qrcode_poll_timeout",
}
for invalid in (0, -1, float("nan"), float("inf"), float("-inf"), 10**1000):
channel = WechatChannel(
bus=MessageBus(),
config={"bot_token": "test-token", **dict.fromkeys(timing_defaults, invalid)},
)
assert {key: getattr(channel, attributes[key]) for key in timing_defaults} == timing_defaults
channel = WechatChannel(bus=MessageBus(), config={"bot_token": "test-token", "polling_retry_delay": "0.25"})
assert channel._retry_delay == 0.25
def test_handle_update_publishes_private_chat_message():
from app.channels.wechat import WechatChannel

View File

@ -1856,11 +1856,12 @@ run_ownership:
# # Optional: sent as SKRouteTag header when provided
# route_tag: ""
# allowed_users: [] # empty = allow all
# # Optional: long-polling timeout in seconds
# # Optional: timing values must be positive finite seconds
# polling_timeout: 35
# # Optional: QR poll interval in seconds when qrcode_login_enabled is true
# polling_retry_delay: 5
# # QR poll interval when qrcode_login_enabled is true
# qrcode_poll_interval: 2
# # Optional: QR bootstrap timeout in seconds
# # QR bootstrap timeout
# qrcode_poll_timeout: 180
# # Optional: persist getupdates cursor under the gateway container volume
# state_dir: ./.deer-flow/wechat/state