Ask
238
@logfile_lena ·

rate limiting a public worker endpoint — the rate limit binding, a durable object, or kv

public signup and a password reset endpoint on a worker, both currently unprotected, and someone spent last tuesday evening hammering the reset one. i want something that stops abuse without me writing an accounting system. i've read that the rate limit binding is per location, which sounds like it means a determined person just gets a fresh bucket in every city.

9 answers Share
Report

Answering anonymously — a moderator will review it first.

  • @borrowck_ben · 5mo ago

    Your reading is right and it matters less than you think. The docs are upfront: limits are local to the Cloudflare location your worker runs in, there's a separate limit per key per location, and the whole thing is described as permissive, eventually consistent and deliberately not an accurate accounting system.

    So yes, in theory an attacker distributed across twenty locations gets twenty buckets. In practice the person hammering your reset endpoint on a Tuesday evening is coming from one place, and the binding stops them with about six lines of config. The other constraint to know before you design around it: the period must be either 10 or 60 seconds. Nothing longer. If you wanted "5 resets per hour per email," that binding cannot express it.

    229
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @crumbshot_carl · 5mo ago · 3 replies

    Which is why I'd split it by what you're actually defending.

    Abuse and noise — the flood, the scraper, the script kiddie — goes to the binding, or to a WAF rate limiting rule in front of the worker so the request never costs you any CPU at all. Business rules — this account may request three resets a day — need to be accurate and global, and that's a durable object keyed by email, or a row in D1 with a counter and a timestamp.

    Trying to make one mechanism do both is how people end up writing an accounting system, which is the thing you said you didn't want to do.

    174
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @rueben_alsop · 5mo ago

      That distinction saves people a lot of wasted work. Abuse defence can be approximate and cheap; product rules have to be exact and cost you a round trip.

      27
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
    • @logfile_lena · 5mo ago

      splitting it by what i'm defending is the framing i was missing. the reset flood is an abuse problem and 'three resets per day per account' is a product rule, and i'd been trying to solve both with one counter.

      41
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @starschema_sy · 5mo ago · 3 replies

    KV is the wrong tool here and I learned it the expensive way. Wrote a counter in KV, incremented on each request, checked before allowing. It looked fine in testing with me clicking.

    Under actual load it let through roughly nine times the limit, because KV reads are cached at the edge and writes take time to propagate globally — every location was reading a stale count and cheerfully allowing more. It's not a bug, it's what KV is for, and I'd used it as a mutex. Durable objects exist precisely because that doesn't work.

    118
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @logfile_lena · 5mo ago

      this was going to be my monday plan, so thank you for taking the bullet. i had it in my head that kv was just a fast small database.

      44
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
    • @r2_bucketeer · 5mo ago

      Fast small database with eventual consistency, which is a fine description right up until two writers care about the same key at the same moment.

      31
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @null_pointer_ok · 5mo ago

    One thing that's cheaper than all of the above for a reset endpoint specifically: stop making the response depend on whether the email exists, and add a delay plus a proof-of-work or turnstile check.

    Most of the value in hammering a reset endpoint is enumeration. Once the endpoint says the same thing in the same time regardless, the attack stops being interesting and your rate limiting only has to handle volume rather than cleverness.

    86
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @reamp_rosa · 3h ago

    Binding for the flood, durable object for the rule, WAF in front if you want it to cost nothing. Ten or sixty seconds only, per location, eventually consistent — design for that and it's genuinely enough for signup abuse.

    1
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report