Context
Follow-up from #13 (duplicate-join detection). Rated Low by the review — not blocking, tracked here.
When a join collides with a live peer, HubState.register returns CONTESTED and calls _announce_system(...) to surface a ⚠️ notice in the operator console (a UI feed event + an append to the bounded message log).
Problem
This path has no rate limit (unlike /send, which is gated by a TokenBucket). A buggy or hostile peer retrying POST /register in a tight loop under a name held by a live listener would:
- flood the operator feed with
⚠️ ... duplicate refused notices, and
- evict real history from the bounded
deque(maxlen=500) message log.
So a single misbehaving client can wipe the recent-message buffer just by hammering register.
Proposed fix
Throttle (or de-duplicate) the contested notice per project: remember the last time a CONTESTED notice fired for a given project name and suppress repeats within a short window (e.g. a few seconds). Clear that bookkeeping when the peer is dropped (_drop) so it doesn't leak.
Keep it lightweight — this is purely a log/feed hygiene guard, not a security boundary.
Acceptance
- Repeated contested registers for the same name within the window produce at most one operator notice.
- The recent-message log is no longer floodable via
/register.
- Per-project throttle state is cleaned up on peer drop (no unbounded growth).
Pointers
src/caucus/state.py — HubState.register (CONTESTED branch), _announce_system, _drop
- Compare with the existing
TokenBucket rate limiting on the /send path
Context
Follow-up from #13 (duplicate-join detection). Rated Low by the review — not blocking, tracked here.
When a join collides with a live peer,
HubState.registerreturnsCONTESTEDand calls_announce_system(...)to surface a⚠️notice in the operator console (a UI feed event + an append to the bounded message log).Problem
This path has no rate limit (unlike
/send, which is gated by aTokenBucket). A buggy or hostile peer retryingPOST /registerin a tight loop under a name held by a live listener would:⚠️ ... duplicate refusednotices, anddeque(maxlen=500)message log.So a single misbehaving client can wipe the recent-message buffer just by hammering register.
Proposed fix
Throttle (or de-duplicate) the contested notice per project: remember the last time a
CONTESTEDnotice fired for a given project name and suppress repeats within a short window (e.g. a few seconds). Clear that bookkeeping when the peer is dropped (_drop) so it doesn't leak.Keep it lightweight — this is purely a log/feed hygiene guard, not a security boundary.
Acceptance
/register.Pointers
src/caucus/state.py—HubState.register(CONTESTED branch),_announce_system,_dropTokenBucketrate limiting on the/sendpath