Skip to content

In-memory rate limiter non-functional on serverless, trivially bypassed via X-Forwarded-For, and missing from ~25 of 30 API routes #69

Description

@tg12

Summary

The in-memory rate limiter in src/lib/rate-limit.ts has two compounding failures that make it non-functional in any real deployment: (1) it uses a module-level Map that is reset on every serverless function cold start — meaning Next.js API routes on Vercel, AWS Lambda, or any serverless platform get a fresh empty rate limit store per invocation; (2) it trusts the X-Forwarded-For header completely without validation, meaning any client can set an arbitrary IP and bypass per-IP limits with zero friction. Even where the rate limiter is wired up (only /api/signals), it provides no real protection.

Evidence

File: src/lib/rate-limit.ts:

// Module-level Map — reset on every serverless cold start
const rateLimitStore = new Map<string, RateLimitEntry>();

function getClientIdentifier(request: Request): string {
  const forwarded = request.headers.get('x-forwarded-for');
  const ip = forwarded?.split(',')[0]?.trim() ||  // ← trusts attacker-controlled header
             request.headers.get('x-real-ip') ||
             'unknown';
  return `${ip}:${userAgent.slice(0, 50)}`;
}

Rate limiter only called in one route out of ~30:

// src/app/api/signals/route.ts — only this route calls applyRateLimit
const rateLimitResponse = applyRateLimit(request);
if (rateLimitResponse) { return rateLimitResponse; }

Routes with NO rate limiting whatsoever:

  • POST /api/notify (Telegram broadcast)
  • POST /api/alerts (Telegram send + SSRF)
  • POST /api/telegram (webhook)
  • GET /api/brief (AI brief generation)
  • GET /api/military-aircraft
  • GET /api/infrastructure (nuclear data)
  • All other ~25 endpoints

Bypass in 1 request:

curl https://feed.xdc.network/api/signals \
  -H 'X-Forwarded-For: 1.2.3.4'
# Fresh rate limit bucket for IP 1.2.3.4

Why this matters

  • An attacker can flood any unprotected endpoint (particularly /api/notify and /api/alerts) with unlimited requests, causing Telegram API rate limits to be hit for the bot token, potentially suspending the bot.
  • The trusted X-Forwarded-For header bypass means even the one protected endpoint (/api/signals) can be hammered without limit.
  • In serverless deployments, the Map is empty at every cold start, so the rate limiter is literally non-functional.
  • Absence of rate limiting on /api/brief means the expensive brief-generation logic can be called continuously.

Root cause

The rate limiter was designed for a long-running Node.js server process (PM2), not for serverless. It was applied to only one endpoint and never validated the X-Forwarded-For header. The in-memory state was never replaced with a persistent store.

Recommended fix

  1. Replace the in-memory Map with a proper persistent rate limit backend:
    • Redis with ioredis (if self-hosted).
    • Upstash Redis (serverless-compatible).
    • Vercel KV / edge middleware rate limiting if deploying to Vercel.
  2. Validate X-Forwarded-For — only trust it when the request comes from a known proxy IP, or use x-real-ip set by your own nginx config.
  3. Apply rate limiting to ALL API routes, especially POST /api/notify, POST /api/alerts, and GET /api/infrastructure.

Acceptance criteria

  • Rate limit state persists across serverless invocations (Redis or equivalent).
  • X-Forwarded-For is not trusted from arbitrary clients.
  • Rate limiting applied to all API routes, not just /api/signals.
  • Verified: repeated requests from the same IP are blocked after the configured limit.

Suggested labels

security, bug

Priority

P0

Severity

High — rate limiter non-functional in serverless, bypassable by header manipulation, and missing entirely from the most abusable endpoints.

Confidence

Confirmed — Map used for storage and X-Forwarded-For trusted unconditionally in current HEAD.


This issue was filed as part of the AI Slop Intelligence Dashboard Awareness Program.
Repo under review: https://github.com/OpenScanAI/GlobeNewsLive
Programme: https://labs.jamessawyer.co.uk/ai-slop-intelligence-dashboards/

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions