#5108
fix(channels): add per-sender message rate limiting
Summary
No channel adapter implements per-user or per-chat message rate limiting. A paired/approved user on any channel could send messages at maximum rate, each consuming LLM tokens, spawning tool executions, and growing session history — with no debounce, no throttling, and no cooldown.
Changes
- Added
nanobot/channels/rate_limit.py: a small in-process sliding-windowRateLimiter(per-minute cap, plus an optional tighter burst cap over a short window). No external dependencies, matching nanobot's existing pairing-store style of lightweight in-process state. - Wired the limiter into
BaseChannel._handle_message()right after the existing authorization check, so every channel gets rate limiting for free with zero per-channel code changes. - Added
channels.rateLimitPerMin(default0, i.e. disabled — fully backwards compatible) andchannels.rateLimitBurst(optional) toChannelsConfig. ChannelManager._build_channelresolves the final limiter the same waysendProgress/sendToolHints/showReasoningare already resolved: a channel section can override the global default, or explicitly opt out withrate_limit_per_min: 0.- DMs get a single friendly cooldown notice per cooldown window (further rejected retries stay silent until the sender is allowed through again, so a burst of rejected messages cannot itself flood the chat). Group/channel messages are dropped silently to avoid drawing attention to the limiter in shared chats.
- Updated
docs/configuration.mdwith a new "Rate Limiting" section under Channel Settings, following the existing documentation style.
Testing
tests/channels/test_rate_limit.py: unit tests for the sliding-window limiter itself (per-minute window, burst window, sliding/recovery behavior, per-key isolation, reset, cooldown message formatting).tests/channels/test_base_channel.py: integration tests throughBaseChannel._handle_message()(disabled by default, blocks over-limit senders, per-sender isolation, single DM cooldown notice vs. silent group drops, burst cap).tests/channels/test_channel_plugins.py: tests forChannelManager's global-default-with-per-channel-override resolution (including camelCase config aliases).- Full test suite passes locally (
pytest -q): 5396 passed, 20 skipped, 1 pre-existing unrelated failure (test_exec_guard_allows_public_urls, fails identically onmainwithout this change — looks like a local network/DNS environment issue, not related to this PR). ruff checkpasses on all changed/added files.
Addresses #4791.
hi @amplifierplus
thanks for your contribution, but nanobot is a personal ai assistant, we don't need rate limit to prevent "dos" attack
see: https://github.com/HKUDS/nanobot/issues/4791#issuecomment-5125869927
closing for now