Skip to content

[BUG] Rate Limiting Silently Becomes a No-Op in Production When Redis/Upstash Is Missing or Hiccups at Startup #861

Description

@ionfwsrijan

Description

src/lib/cache.ts:

  • Lines 281-319 pickDefaultBackend(): if neither KV_REST_API_URL+KV_REST_API_TOKEN nor REDIS_URL is set, it returns MemoryBackend — and in production only logs console.error (lines 310-317) before continuing.
  • Lines 290-301: even when Redis is configured, the ioredis client uses maxRetriesPerRequest: 1 and retryStrategy: () => null, and on the first connection error permanently swaps the singleton backend = new MemoryBackend() (line 298) — the instance never recovers.
  • Lines 322-326: isSharedCacheAvailable() is only used to print a warning.

src/lib/rate-limit.ts:40-53 then delegates all sliding-window counting to whatever backend is live. Consumers:

  • Webhook receiver src/app/api/webhooks/github/route.ts:58-67 (100/min per install, the only webhook DoS guard)
  • Every authenticated server action via src/lib/action-auth.ts:41-51
  • Dead-letter retry src/app/api/webhooks/github/retry/route.ts:68-77 (10/min)

Expected Behavior

On a serverless platform (each invocation is an isolated process), rate-limit state must be shared and durable; a misconfiguration or Redis outage must fail closed, not silently pass.

Actual Behavior

With MemoryBackend, counters live in a per-invocation map. Rate limits are effectively disabled — a single user can hammer the webhook endpoint, retry endpoint, or any action without being throttled, because each serverless instance resets its own map. Worse, the failure is silent (console log only), so the webhook 429 protection and all per-user action throttles can be bypassed during exactly the deployment scenarios (forgotten KV vars, Redis blip) where they matter most.

Affected Files

  • src/lib/cache.ts (lines 281-319, 290-301, 322-326) — Memory fallback + permanent swap on Redis error
  • src/lib/rate-limit.ts (lines 40-53) — Delegates to whichever backend is live
  • src/app/api/webhooks/github/route.ts (lines 58-67) — Webhook rate limit
  • src/lib/action-auth.ts (lines 41-51) — Action rate limit
  • src/app/api/webhooks/github/retry/route.ts (lines 68-77) — Dead-letter retry limit

Proposed Fix

  1. Make rate-limiting fail closed: if NODE_ENV === 'production' and no distributed backend is available, rateLimit() should return ok: false (block) rather than pass
  2. Remove the permanent backend swap on Redis error; add a reconnect/health-check path that re-attempts Redis
  3. Treat missing KV in production as a boot/deploy failure (fail the middleware gate or surface a prominent startup error), not a degraded runtime state

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions