ROBIN VAULT

Rate Limiting Policy

The exact formula that turns an offline brute-force problem into a bounded, online one.

The formula

  • The first 5 requests in a streak (n = 1..5) are always allowed, free.

  • From the 6th request onward (n ≥ 6):

    lockoutSeconds = min(30 days, 30 × 2^(n − 6))
    
  • 24 hours of silence resets the attempt counter to 0 — this is the only unlock mechanism. There is no admin override: anything that could bypass the lockout could also be tricked into leaking the salt.

Attempt (n)Lockout
1–5allowed (free)
630s
760s
8120s
9240s
10480s
doubling each attempt
~2230 days (cap reached)

No existence-timing oracle

Both allowed and denied requests increment the counter and advance the timestamp — this applies identically to unknown, never-provisioned accountIds and to known, locked ones. An attacker cannot distinguish "this account doesn't exist" from "this account is locked" by response cost or shape.

Dev-only bypass

A DISABLE_RATE_LIMIT environment variable exists purely for local development — it disables the gate entirely and persists no state, logging a startup warning when active. It must never be set in production; doing so removes the only defense against offline PIN brute force.

Redis vs. sqlite

Locally, this state lives in sqlite behind an atomic transaction. On Vercel, it's Redis-backed via a plain read-then-write (Upstash's REST API has no transaction primitive) — the same pure policy function runs either way, but the Redis path can under-count concurrent hits by one. It can never over-count into a false lockout. See Deployment Architecture for the full picture.