You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix: fail closed when rate limiter bindings are missing or erroring - #48
The allowed() helper in worker/index.ts silently failed open: if the SUBSCRIBE_IP_LIMIT / SUBSCRIBE_GLOBAL_LIMIT bindings were missing or the limit() call threw, every request was treated as within limits, leaving the /api/subscribe endpoint (and the Resend API quota behind it) unprotected with
no trace in the logs.
This PR replaces allowed() with a checkLimit() helper that returns a
tri-state result:
"ok" - within limits, request proceeds (unchanged)
"limited" - over the limit, returns 429 (unchanged)
"error" - binding missing or limiter threw: logs the failure with console.error (naming the offending binding) and returns 503 "Signups are unavailable right now.", consistent with how other missing
configuration (e.g. RESEND_API_KEY, TURNSTILE_SECRET_KEY) is handled
Misconfiguration now surfaces loudly in wrangler tail instead of failing
silently, and a limiter outage degrades to "signups temporarily unavailable"
rather than "no rate limiting at all".
Type of change
Bug fix
New component
Component improvement
Docs / site
Other
Checklist
npx tsc --noEmit passes
npm run lint passes
npm run build passes
Tested in Chrome (with the html-in-canvas flag, if relevant)
Tested the WebGL overlay fallback (flag off / Safari / Firefox), if a component changed
Note: the change is worker-only (no components touched). I was unable to run
the checks locally due to network issues installing dependencies - relying on
CI; happy to follow up if anything fails.
Hey David, circling back on this one when you get a chance.
The only real judgment call here is what /subscribe should do when the
limiter itself is unavailable. Right now allowed() returns true both when
the binding is missing and when limit() throws, so a limiter outage
quietly removes the abuse protection. This changes that to a 503, which
matches how the endpoint already treats missing config a few lines above,
RESEND_API_KEY and TURNSTILE_SECRET_KEY both return 503 UNAVAILABLE.
The trade-off is availability: a limiter hiccup would block real signups
instead of letting a few extra through. If you'd rather stay up in that
case, I can narrow it, fail closed when the binding is absent (so a bad
deploy can't silently disable limits) but keep the catch path failing
open with a log line. That's a one-line change either way.
No rush, I just didn't want to leave the decision sitting unanswered.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
The
allowed()helper inworker/index.tssilently failed open: if theSUBSCRIBE_IP_LIMIT/SUBSCRIBE_GLOBAL_LIMITbindings were missing or thelimit()call threw, every request was treated as within limits, leaving the/api/subscribeendpoint (and the Resend API quota behind it) unprotected withno trace in the logs.
This PR replaces
allowed()with acheckLimit()helper that returns atri-state result:
"ok"- within limits, request proceeds (unchanged)"limited"- over the limit, returns 429 (unchanged)"error"- binding missing or limiter threw: logs the failure withconsole.error(naming the offending binding) and returns 503"Signups are unavailable right now.", consistent with how other missingconfiguration (e.g.
RESEND_API_KEY,TURNSTILE_SECRET_KEY) is handledMisconfiguration now surfaces loudly in
wrangler tailinstead of failingsilently, and a limiter outage degrades to "signups temporarily unavailable"
rather than "no rate limiting at all".
Type of change
Checklist
npx tsc --noEmitpassesnpm run lintpassesnpm run buildpassesNote: the change is worker-only (no components touched). I was unable to run
the checks locally due to network issues installing dependencies - relying on
CI; happy to follow up if anything fails.