Skip to content

fix(faucet): rate-limit the faucet to prevent testnet drain - #2

Merged
MehranMazhar merged 1 commit into
mainfrom
fix/faucet-rate-limit
Jul 23, 2026
Merged

fix(faucet): rate-limit the faucet to prevent testnet drain#2
MehranMazhar merged 1 commit into
mainfrom
fix/faucet-rate-limit

Conversation

@MehranMazhar

Copy link
Copy Markdown
Member

What

Rate-limits POST /faucet to stop testnet faucet drain. Previously any
caller could hit the endpoint repeatedly and drain the funded faucet account.

How

In-memory cooldown limiter (src/hub/rate_limit.rs), std only — no new dependency:

  • Per client IP — 30s window (anti-spam). Uses realip_remote_addr() so it
    reads X-Forwarded-For/X-Real-IP behind the nginx reverse proxy, not the proxy IP.
  • Per recipient address — 1h window (anti-refund drain), case-insensitive.

Rejected requests return 429 Too Many Requests + Retry-After. A rejected hit is
not recorded, so a blocked caller can't push its own cooldown forward. The limiter
is instantiated once and shared across all Actix workers (per-worker would multiply the
limit by worker count). The map self-prunes to the longest window.

Out of scope

  • Config-tunable windows (hardcoded constants, marked with ponytail: comments).
  • Captcha / PoW and per-period amount caps — an attacker rotating both IP and address
    still bypasses a keyless faucet.

Testing

cargo test — 21 passed (19 existing + 2 new: cooldown + case-insensitivity).

POST /faucet had no rate limiting, so any caller could repeatedly drain
the funded testnet faucet account.

Add an in-memory cooldown limiter (std only, no new dependency) keyed by:
- client IP: 30s window (anti-spam), read via realip_remote_addr so it
  uses X-Forwarded-For behind the nginx reverse proxy, not the proxy IP;
- recipient address: 1h window (anti-refund drain), case-insensitive.

Rejected requests return 429 with a Retry-After header and do not record
a hit, so a blocked caller cannot push its own cooldown forward. The
limiter is created once and shared across all Actix workers (a per-worker
instance would multiply the limit by the worker count). The map
self-prunes to the longest window to stay bounded.

Cooldowns are fixed constants for now (marked with ponytail: comments as
the tuning knob). Captcha/PoW and per-period amount caps are out of scope:
an attacker rotating both IP and address still bypasses a keyless faucet.

Adds unit tests for the cooldown and case-insensitivity behavior.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@MehranMazhar
MehranMazhar merged commit 5e87ed8 into main Jul 23, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant