← All posts
rate limitingvibe-coded appssecurityNext.jsAPI

How to Fix Insecure Rate Limiting in Vibe-Coded Apps

O

OverMCP Team

Quick answer

Rate limiting is a critical security control that prevents brute-force attacks, credential stuffing, and API abuse by limiting how many requests a user can make in a given time window. Vibe-coded apps often skip it because AI assistants rarely add it unless explicitly asked. To fix insecure rate limiting, you need to add a middleware or library that tracks requests per user or IP, and returns a 429 status when limits are exceeded.

What to check first

Before implementing rate limiting, check if your app already has some form of it:

  • Look for existing middleware: If you're using Express, check for express-rate-limit; if Next.js, check for middleware or API route handlers that count requests.
  • Inspect your API routes: If your routes are simple functions that directly handle requests without any throttling logic, you likely have no rate limiting.
  • Search for `rate` or `limit` in your codebase: Use grep -r "rateLimit\|rate-limit\|throttle" to see if any library or custom code exists.
  • Test your app: Make 100 rapid requests to a login endpoint. If you get 200 OK responses for all, you're vulnerable.
  • Check server logs: If you see a single IP making hundreds of requests per minute without any blocks, you're exposed.
  • Step-by-step fix

    1. Choose a rate limiting strategy

    There are two common approaches:

  • In-memory store: Simple but not suitable for multi-instance deployments because each instance has its own counter.
  • Distributed store: Use Redis or a database to share counters across instances. Best for production apps.
  • 2. Implement in Next.js (App Router)

    If you're using Next.js with API routes, you can add rate limiting in a middleware or inside each route handler. Here's a simple in-memory rate limiter for a single-instance app:

    // middleware.js
    import { NextResponse } from 'next/server'
    
    const rateLimit = new Map()
    
    const WINDOW_MS = 60 * 1000 // 1 minute
    const MAX_REQUESTS = 100
    
    export function middleware(request) {
      const ip = request.ip ?? 'anonymous'
      const now = Date.now()
      const entry = rateLimit.get(ip)
      
      if (!entry || now - entry.start > WINDOW_MS) {
        rateLimit.set(ip, { start: now, count: 1 })
        return NextResponse.next()
      }
      
      entry.count += 1
      if (entry.count > MAX_REQUESTS) {
        return new NextResponse('Too Many Requests', { status: 429 })
      }
      
      return NextResponse.next()
    }
    
    export const config = {
      matcher: '/api/:path*',
    }

    For production with multiple instances, use Redis:

    // lib/rateLimit.js
    import { Redis } from '@upstash/redis'
    
    const redis = new Redis({
      url: process.env.UPSTASH_REDIS_REST_URL,
      token: process.env.UPSTASH_REDIS_REST_TOKEN,
    })
    
    export async function rateLimit(identifier, limit, windowSeconds) {
      const key = `rate_limit:${identifier}`
      const current = await redis.incr(key)
      if (current === 1) {
        await redis.expire(key, windowSeconds)
      }
      return current <= limit
    }

    Then use it in your API route:

    // app/api/login/route.js
    import { rateLimit } from '@/lib/rateLimit'
    
    export async function POST(request) {
      const ip = request.headers.get('x-forwarded-for')?.split(',')[0] ?? 'anonymous'
      const allowed = await rateLimit(`login:${ip}`, 5, 60) // 5 requests per minute
      if (!allowed) {
        return Response.json({ error: 'Too many requests' }, { status: 429 })
      }
      // handle login
    }

    3. Apply rate limiting to sensitive routes

    Focus on routes that are vulnerable to brute-force attacks:

  • Login/authentication: Limit to 5-10 requests per minute per IP.
  • Password reset: Limit to 3 per hour.
  • Registration: Limit to a few per hour.
  • Payment endpoints: Limit to prevent payment abuse.
  • Search APIs: Limit to prevent scraping.
  • 4. Return proper headers

    Include X-RateLimit-Limit, X-RateLimit-Remaining, and Retry-After headers so clients know when they can retry.

    5. Test your implementation

    Use a tool like ab or Postman to send many requests and verify you get 429 responses.

    Common mistakes

  • No rate limiting at all: The most common issue in vibe-coded apps. AI assistants don't add it unless you ask.
  • Rate limiting only on login: Attackers can target other endpoints like user enumeration or search.
  • Using in-memory store in production: If you run multiple instances, your counters are not shared, allowing bypass.
  • Blocking by IP only: Attackers can rotate IPs. Consider using user ID or a combination.
  • Not returning 429 with Retry-After: Clients may keep retrying, increasing load.
  • Setting limits too high: If you allow 1000 requests per minute, you're still vulnerable.
  • Forgetting to exclude health checks: Your monitoring might trigger rate limits.
  • FAQ

    How do I choose the right rate limit numbers?

    Start with your legitimate user's behavior. If your API is for a single user, 100 requests per minute is fine. For public APIs, consider 60-100 per minute per IP. For login, 5 attempts per minute is standard. Monitor your logs and adjust.

    Can I use a third-party service like Cloudflare?

    Yes, but if your app is behind a reverse proxy, ensure you trust the X-Forwarded-For header. Otherwise, you can use services like Cloudflare, but for many indie apps, a simple in-app rate limiter is sufficient.

    My app is built with Cursor, how do I know if it already has rate limiting?

    Search for rateLimit, express-rate-limit, limiter, or throttle in your codebase. If you don't find any, you likely don't have it. Also check your API routes for any conditional that checks request count.

    ---

    You can use our [free AI app security scanner](https://www.overmcp.com/) to detect missing rate limiting and other vulnerabilities in your vibe-coded app. Also check your [security headers](https://www.overmcp.com/tools/headers) and [SSL certificate](https://www.overmcp.com/tools/ssl) to ensure basic security is in place. If you deploy on Vercel, our [Vercel security scanner](https://www.overmcp.com/connect/vercel) can help you continuously monitor your app. For ongoing protection, consider [continuous security monitoring](https://www.overmcp.com/monitor) to catch issues early.

    Is your app secure?

    Free scan in 30 seconds. No signup needed.

    Scan My App Free