Skip to content

[SEC-V-05 followup] LRU eviction + timer sweep for ProfileState/typing_peers maps #429

@intendednull

Description

@intendednull

Context

Followup to #234 / [SEC-V-05]. The first PR for #234 caps individual entries (display name + typing channel) at 128 chars on receipt, which bounds per-entry memory. This issue tracks the remaining two mitigations needed to fully close the unbounded-growth surface.

The maps in question are ephemeral — never persisted to the DAG, but they live for the entire process lifetime, so an attacker who can deliver wire messages can still grow them by count even with per-entry caps in place:

  • state_actors::ProfileState.names: HashMap<EndpointId, String> — one entry per ProfileAnnounce sender. Each fresh attacker key = one new map entry. Last-write-wins per key, so old entries stick around forever.
  • state_actors::NetworkMeta.typing_peers: HashMap<EndpointId, (String, u64)> — one entry per peer that ever sent a TypingIndicator. Stale entries are filtered in the view layer (TYPING_INDICATOR_TTL_MS) but never removed from the map.

Work to do

  1. Timer sweep on typing_peers. Drop entries older than ~TYPING_INDICATOR_TTL_MS (5s) on a periodic tick. Either piggy-back on the presence tick driver in connect.rs or run a small dedicated task. Today the view filters stale entries on render — which means the map is the source of truth for "everyone who ever typed" and grows unboundedly even with the per-entry cap from PR [SEC-V-05] ProfileState.names / ChatMetaState.typing_peers accept unbounded attacker-supplied strings #234.
  2. Total-entries LRU cap on both maps. Pick a reasonable upper bound (suggested: ~10k entries each — comfortable headroom for a busy server but bounds memory at a few MB even with worst-case 128-char names). On overflow, evict the least-recently-touched entry. Crate lru is already used elsewhere in the workspace if we want a ready-made impl, otherwise a small hand-rolled wrapper around IndexMap is fine.
  3. Add tests:
    • typing-sweep: insert N entries, advance time past TTL, run sweep, assert map empty.
    • LRU profile cap: insert cap + 1 distinct senders, assert map size == cap and oldest sender's entry is gone.

Notes

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions