# Rate limiting middleware imported is actually applied

- **Pattern:** `ab-000281` (`ai-slop-security-theater.unapplied-middleware.rate-limiter-imported-and-applied`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000281
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000281)

## Why it matters

Constructing an `@upstash/ratelimit` or `express-rate-limit` instance and never calling `.limit()` or `app.use(rateLimit(...))` leaves your endpoints open to credential stuffing, enumeration, and API cost amplification. CWE-400 (Uncontrolled Resource Consumption) is the direct mapping. The library's presence in `package.json` signals the developer anticipated abuse — but without invocation, every endpoint accepts unlimited requests. An attacker who discovers the rate limiter is never called can run brute-force or scraping at full speed.

## Severity rationale

High because an unapplied rate limiter provides zero protection against brute-force, credential stuffing, and API resource exhaustion on every route.

## Remediation

Call `.limit()` per request at the top of each handler, or apply `rateLimit()` as global middleware. For Upstash:

```ts
import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis'

const limiter = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, '10 s'),
})

export async function POST(req: Request) {
  const ip = req.headers.get('x-forwarded-for') ?? 'anonymous'
  const { success, remaining } = await limiter.limit(ip)
  if (!success) {
    return Response.json({ error: 'Too many requests' }, { status: 429 })
  }
  // ... rest of handler
}
```

For sensitive routes (login, password reset, signup), apply tighter limits than general API routes. Verify the `.limit()` call appears before any business logic.

## Detection

- **ID:** `rate-limiter-imported-and-applied`
- **Severity:** `high`
- **What to look for:** When any rate-limiting library is in `package.json` dependencies — `express-rate-limit`, `@upstash/ratelimit`, `rate-limiter-flexible`, `rate-limit-redis`, `bottleneck`, `next-rate-limit`, `@vercel/firewall` — walk source files and count all imports of these libraries. For each import, count whether the same file or another file in the application chain contains an INVOCATION pattern: `app.use(rateLimit(`, `await ratelimit.limit(`, `await rateLimit.check(`, `await rateLimiter.consume(`, `bottleneck.schedule(`, OR a middleware wrapper that the library documents.
- **Pass criteria:** A rate-limiting library is imported AND at least 1 invocation call exists. Report: "Rate limiter: [name] imported in [file] AND invoked in [file]."
- **Fail criteria:** A rate-limiting library is imported but never invoked anywhere in source.
- **Skip (N/A) when:** No rate-limiting library in dependencies AND no `vercel.json`/`netlify.toml`/Cloudflare config defines rate limits.
- **Detail on fail:** `"@upstash/ratelimit imported in src/lib/ratelimit.ts but no .limit(... call found anywhere in source. Rate limiter constructed but never used to actually limit requests."`
- **Remediation:** A rate limiter that's never invoked doesn't limit anything. Wire it into your route handlers:

  ```ts
  // Bad: limiter constructed but never called
  import { Ratelimit } from '@upstash/ratelimit'
  const limiter = new Ratelimit({ ... })
  // ...no .limit() calls anywhere

  // Good: invoke .limit() per request
  export async function POST(req: Request) {
    const ip = req.headers.get('x-forwarded-for') ?? 'anonymous'
    const { success } = await limiter.limit(ip)
    if (!success) {
      return Response.json({ error: 'Too many requests' }, { status: 429 })
    }
    // ... handle request
  }
  ```

## External references

- cwe CWE-400
- owasp A04

Taxons: placeholder-hygiene, cost-efficiency

HTML version: https://auditbuffet.com/patterns/ab-000281
