#5108

fix(channels): add per-sender message rate limiting

Openamplifierpluswants to merge intofrom

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-window RateLimiter (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 (default 0, i.e. disabled — fully backwards compatible) and channels.rateLimitBurst (optional) to ChannelsConfig.
  • ChannelManager._build_channel resolves the final limiter the same way sendProgress/sendToolHints/showReasoning are already resolved: a channel section can override the global default, or explicitly opt out with rate_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.md with 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 through BaseChannel._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 for ChannelManager'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 on main without this change — looks like a local network/DNS environment issue, not related to this PR).
  • ruff check passes on all changed/added files.

Addresses #4791.

Activity

2
chengyongru3mo ago

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