Implement silent rate limiting for chat spam and guess-botting prevention
Adds rate limiting to prevent chat spam and automated word guessing. Rate-limited players see their own messages but they're not broadcast to others ("gaslighting" approach per maintainer request).
Changes
Rate limits:
- 5 messages/second (burst)
- 30 messages/20 seconds (sustained)
Player struct (internal/game/shared.go):
- Added
messageTimestamps []time.Timeto track message history
Rate limiting logic (internal/game/lobby.go):
-
isRateLimited(): Checks both rate limits with optimized timestamp cleanup (in-place filtering) and early-break iteration for per-second counting -
recordMessage(): Appends timestamp to player history - Modified
handleMessage(): Rate-limited messages only sent to sender; correct guesses rejected when rate-limited
Tests (internal/game/lobby_test.go):
- 7 unit tests covering limit thresholds, timestamp cleanup, and edge cases
Example
// Before: all messages broadcast unconditionally
func handleMessage(message string, sender *Player, lobby *Lobby) {
lobby.broadcastMessage(trimmedMessage, sender)
}
// After: rate-limited messages only visible to sender
func handleMessage(message string, sender *Player, lobby *Lobby) {
rateLimited := isRateLimited(sender)
recordMessage(sender)
if rateLimited {
// Silent limiting: sender sees message, others don't
lobby.WriteObject(sender, newMessageEvent(...))
} else {
lobby.broadcastMessage(trimmedMessage, sender)
}
}
Original prompt
This section details on the original issue you should resolve
<issue_title>Chat spam protection</issue_title> <issue_description>EDIT Maybe this requires a general rate-limiting logic that works per API-Call-Type. This way for example player X could send an event of type "Y" up to Z times per W second. This would prevent spam and it would also prevent guess botting. As the word-lists are open, it's technically possible to spam the whole word list. Especially when scribble-rs/scribble.rs#2 gets implemented, this will become a problem. On top of that, some people might experience lag when chat-spamming too hard. There was one case for example where one's password manager was manipulating the document on each document update. While this isn't exactly our problem, it's apparently something we could look out for.</issue_description>
<agent_instructions>Create a rate limiting feature, that rejects word guesses and doesn't broadcast messages to other players. The spamming player, should still see their messages, they should NOT notice they are being limited.</agent_instructions>
Comments on the Issue (you are @copilot in this section)
@Bios-Marcel I am somewhat unsure on this now. I don't really know a sensible limit. There are people that can type very fast, meaning they can technically smash out about 4 words per second. However, in a 120 second round this would be 480 words, with which a bot could easily guess the word correctly, depending on the word list size and the letter count.Now, the question is, what kind of rate limiting should we impose on the player. Maybe something like "Up to 5 guesses a second and up to 30 in 20 seconds". This would greatly decrease the maximum amount of guesses, but allow short time spamming, which can be viable. However, it's rather unlikely someone would keep spamming guesses this quickly. So this might be fine.
On top of that we probably need to limit the message length to something sensible. Since I'd still like to allow chatter, a message should probably not take up more than 6-7 lines.</comment_new> <comment_new>
Another thought. Rate limiting should be silent, if you are running into the limit, others shouldn't be able to see your messages anymore. We'll gaslight you into thinking you are guessing wrong as well.@Bios-Marcel Fight clowns with clown moves.</comment_new>
- Fixes scribble-rs/scribble.rs#15
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.