Skip to content

[Security]: Use a distributed rate limiter for portal login attempts #24

Description

@awatchar

Problem

The portal login limiter is process-local, so counters reset across serverless instances and cold starts.

Scope

  • Move login throttling to shared production storage with atomic updates.
  • Use a privacy-safe bounded key and expiry.
  • Keep deterministic in-memory behavior for local/test environments where appropriate.
  • Define explicit fail-safe behavior when shared storage is unavailable.
  • Do not change the accepted Device-ID configuration delivery model.

Acceptance

  • Concurrent attempts across simulated instances share one limit.
  • Expiry, successful-login reset and storage failure behavior are tested.
  • No raw password, session token or unnecessary full IP address is stored.
  • Portal tests, typecheck and production build pass.
  • Production deployment and unauthenticated smoke check succeed after merge.

Activity

  1. awatchar commented on Aug 24, 2026

    @awatchar
    OwnerAuthor

    Implementation started on branch fix/lab-correctness-batch. The design is being checked against the current Vercel/Cloudflare storage architecture so the limiter is genuinely shared across serverless instances, not another process-local counter.

  2. awatchar commented on Aug 24, 2026

    @awatchar
    OwnerAuthor

    Implementation update: a dedicated production D1 database has been created and migration 0001 applied. The exact atomic UPSERT/RETURNING contract was exercised in Cloudflare D1 Console against a temporary HMAC-shaped bucket and the probe row was deleted afterward. Local web tests (120/120), TypeScript, and production build pass. Independent review identified two hardening items now being addressed before PR/deploy: do not advance the account bucket after a client is already blocked, and bound expired bucket-row retention. Production token/HMAC environment provisioning and concurrent REST acceptance remain pending the required action-time secret authorization.

  3. awatchar commented on Aug 24, 2026

    @awatchar
    OwnerAuthor

    Implementation is ready in PR #26, but must not be merged/deployed yet because production intentionally fails closed until its three Vercel variables are installed.

    What changed:

    • Cloudflare D1 atomic fixed-window client gate runs before KV/admin lookup (10 attempts / 15 min)
    • configured-admin or bounded decoy account gate runs before password verification (30 / 15 min)
    • successful and failed attempts both consume quota
    • bucket keys are HMAC-only; logs and errors are generic
    • malformed/absent forwarded IP uses one bounded unknown bucket
    • expired rows are pruned by schema triggers
    • D1 timeout/malformed/error paths fail closed with 503; local tests use deterministic memory storage

    The production D1 schema, index, trigger and exact UPSERT/RETURNING behavior have been created and live-probed. Portal verification passes 124 tests, TypeScript and production build.

    Remaining operational gate: provision CLOUDFLARE_D1_DATABASE_ID, a least-privilege CLOUDFLARE_D1_API_TOKEN, and LOGIN_RATE_LIMIT_KEY_SECRET in Vercel, deploy, then smoke-test 429/503/login behavior.

  4. awatchar commented on Aug 24, 2026

    @awatchar
    OwnerAuthor

    Production activation completed after PR #26 merge.

    • Account-owned Cloudflare token is limited to D1 Write.
    • All three D1 variables are Sensitive and enabled for Production + Preview.
    • Exact PR Preview passed live admission smoke: attempts 1-10 returned generic 401 and attempt 11 returned 429.
    • Production deployment is Ready; root is 200, session reports configured=true, and production uses the same D1 bucket successfully.
    • The temporary Preview-build override was restored to Only build production.

    Closing because implementation, deployment, and smoke acceptance are complete.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions