5c11947bd7ecf603ad3d3af4cac931c807d1a23d
A room ticket holder is an authenticated insider; signatures only prove keypair ownership, not a distinct human. Previously such a member could exhaust a victim's memory/tasks/dials without bound. Add caps + dedup at the gossip/core/UI boundaries (no wire/protocol change, no new deps): F-01 (gossip): cap the roster at MAX_ACTIVE_PEERS (32) — new authors are rejected when full, existing peers' updates always pass; sanitize each announced EndpointAddr (<=8 addrs, relay-URL <=256 bytes, drop Custom); replace (set_endpoint_info) instead of unioning attacker address history. F-02 (core): gate chat image auto-fetch — only roster authors qualify, (author, attachment_id) is deduped, and a 4-permit pool bounds concurrent detached fetch tasks (RAII AutoFetchGuard releases permit + dedup marker). Chat text is still shown (already sanitized); the user-initiated "Save" fetch is unchanged. Non-roster sock-puppet chat can no longer spawn tasks. F-03 (app): replace the unbounded AVATAR_HANDLE_CACHE map with a bounded, byte-equality-keyed LRU (avatar::ByteLru, cap 64) — fixes both unbounded growth from an endless stream of distinct valid avatars and the prior 64-bit-hash-collision-shows-wrong-avatar bug. Pure seams (sanitize_endpoint_addr, admit_into_roster, should_auto_fetch, ByteLru) + 6 adversarial/unit tests. 413 lib tests, clippy --all-targets clean, release build green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>