Hmbown/CodeWhale · critical · Error

Missing login limiter

Error message

Missing login limiter

What it means

The admin login route gates every login attempt behind a Cloudflare rate-limit binding, env.ADMIN_LOGIN_LIMITER. If the binding is absent the code deliberately throws instead of allowing logins ungated — an unavailable limiter must not disable the gate.

Solutions

  1. Add the ADMIN_LOGIN_LIMITER rate-limiting binding to wrangler.toml/wrangler.jsonc and redeploy.
  2. Run the route through `wrangler dev` (or Miniflare) so bindings are available instead of plain `next dev`.
  3. Verify with `wrangler deployments list` / `wrangler versions view` that the deployed version carries the binding.

Example fix

// before (wrangler.jsonc)
{
  "compatibility_date": "2025-01-01"
}
// after (wrangler.jsonc)
{
  "compatibility_date": "2025-01-01",
  "ratelimits": [{ "name": "ADMIN_LOGIN_LIMITER", "namespace_id": "1001" }]
}
Defensive patterns

Strategy: fallback

Try / catch

try {
  const res = await fetch("/api/admin/login", { method: "POST", body: creds });
} catch (err) {
  if (String(err.message).includes("Missing login limiter")) {
    // Deployment misconfiguration: verify Cloudflare bindings before retrying.
    reportDeploymentConfigError(err);
  }
}

Prevention

When it happens

Trigger: POST /api/admin/login when the deployed Worker/Pages environment lacks the ADMIN_LOGIN_LIMITER binding (e.g. wrangler.toml missing the rate limiting binding, or a local dev run without `wrangler dev` bindings).

Common situations: Deploying web/ without updating wrangler config after the limiter was introduced; running `next dev` locally where Cloudflare bindings do not exist; preview environments created from an outdated config.

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/26830007382aa3bc. Report an issue: GitHub.

Appendix: source

Thrown at web/app/api/admin/login/route.ts:32

}

export async function POST(req: Request) {
  const env = await getAgentEnv();
  const url = new URL(req.url);
  const localeFromQuery = pickLocale(url.searchParams.get("locale"));

  if (!env.MAINTAINER_TOKEN) {
    return new NextResponse("Not configured", {
      status: 503,
      headers: { "Cache-Control": "no-store" },
    });
  }

  // One maintainer principal: key by that account, never by attacker-controlled
  // headers or submitted tokens. The binding shares counters across isolates
  // in a Cloudflare location. An unavailable limiter must not disable the gate.
  try {
    if (!env.ADMIN_LOGIN_LIMITER) throw new Error("Missing login limiter");
    const { success } = await env.ADMIN_LOGIN_LIMITER.limit({ key: "codewhale-web:admin-login" });
    if (!success) {
      return new NextResponse("Too many login attempts", {
        status: 429,
        headers: { "Cache-Control": "no-store", "Retry-After": "60" },
      });
    }
  } catch {
    return new NextResponse("Login temporarily unavailable", {
      status: 503,
      headers: { "Cache-Control": "no-store", "Retry-After": "60" },
    });
  }

  let form: URLSearchParams;
  try {
    form = await readBoundedUrlEncodedForm(req, MAX_LOGIN_BODY_BYTES);
  } catch (error) {

View on GitHub (pinned to 433685b202)