DoS Lab 02 — Rate-Limiter Simulator

WARMUP: Chapter 6 (rate-limiting algorithms).

The problem

Rate limiting is the workhorse application-layer availability control — and a defense against credential stuffing, brute force, scraping, and enumeration. The failure modes are subtle: a fixed-window counter permits a ~2× boundary burst, and a single global counter lets one client starve everyone. This lab implements the algorithm most APIs actually want — a token bucket (bounds the sustained rate while allowing controlled bursts) — plus a sliding-window limiter, both with per-key isolation. Time is supplied, so behavior is deterministic and testable.

What you build (lab.py)

  • TokenBucketLimiter(capacity, refill_rate)allow(key, now) and tokens(key, now): refill to now, consume a token if available, cap at capacity.
  • SlidingWindowLimiter(limit, window)allow(key, now): at most limit requests in any trailing window seconds.
  • simulate(limiter, events) — run (key, time) events and return the allow/deny decisions.

Attack cases the tests cover

  • Token bucket: a full bucket allows a burst of capacity, then denies; tokens refill over time; they cap at capacity (not unbounded after idle); the sustained rate is bounded to refill_rate; one key's exhaustion doesn't affect another (per-key isolation).
  • Sliding window: limits within the window and frees requests once they age out; per-key.

Run

pip install -r requirements.txt
LAB_MODULE=solution pytest -q
pytest -q

Hardening / extensions

  • Add fixed-window and leaky-bucket variants and compare boundary-burst behavior.
  • Make it distributed (a shared store) and discuss atomicity (the P02 web-race lesson).
  • Return 429 + Retry-After semantics and a per-tenant quota tier.
  • Add cost-weighted requests (an expensive endpoint consumes more tokens).

Interview / resume

"Implemented token-bucket and sliding-window rate limiters with per-key isolation — bounding the sustained rate while allowing controlled bursts — the core L7 availability and anti-abuse control, and explained why fixed-window/global counters fail."

Limitations: in-memory single-process simulation with supplied time (not a distributed, wall-clock implementation); the leaky-bucket/fixed-window variants are left as extensions.