Skip to content

Relay DoS hardening: per-IP governor + allocation caps + ungoverned read endpoints (audit H1/H2/M3/M4) #291

@laulpogan

Description

@laulpogan

Summary

From a focused security audit of src/relay_server.rs. The relay's DoS posture rests on a single global rate bucket plus a per-slot byte quota; several routes have no governor at all, and there is no cap on the number of objects a caller can create. Grouped because they share a root (no per-IP keying + unbounded allocation) and are best fixed together.

H2 — global rate-limit bucket (shared-fate DoS)

relay_server.rs :384 — the governor uses GlobalKeyExtractor: one shared bucket for /v1/slot/allocate, /v1/pair, /v1/pair/:id/bootstrap, /v1/pair/abandon. One client hammering allocate consumes the global 10/s + 50 burst, so every other agent's pair/allocate calls 429. The rate-limiter becomes the DoS vector.
Note: the code comment (
:376) flags per-IP as a deliberate v0.2 deferral ("Cloudflare WAF + global cap in series"). Real for self-hosted / local relays with no edge WAF.
Fix: per-IP key_extractor via ConnectInfo (SmartIpKeyExtractor).

H1 — unbounded allocation (RAM + disk exhaustion)

allocate_slot (:782, unauthenticated), pair_open (:999), invite_register (:1375), handle_claim (:1156). No cap on the count of slots/handles/pairs/invites per caller; the in-RAM HashMaps grow without bound, and tokens.json is rewritten whole on every allocation (O(n) write amplification).
Fix: per-IP cap + a hard global ceiling on object counts; batched/append token persistence.

M3 — /v1/handles deep-clones the whole handle map, ungoverned

~:1553inner.handles.values().cloned().collect() (each record carries a full card) then sorts, on every request, with no governor (route at ~:434). Quadratic amplifier with H1.
Fix: put read endpoints under the (per-IP) governor; paginate from a sorted index.

M4 — /stats.history re-reads an unbounded file, ungoverned

~:622read_to_string + per-line JSON parse of stats-history.jsonl (grows ~720 KB/day, prune is a deferred "future wave"), ungoverned (route ~:430).
Fix: implement the prune; cap read size; rate-limit.

Not bugs (verified)

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions