# Batch submission attempts above 5 per 60 seconds trigger temp block and admin alert

- **Pattern:** `ab-001086` (`directory-submissions-moderation.spam-prevention.rate-limiting-batch`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001086
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001086)

## Why it matters

Without batch rate limiting, a single IP can submit hundreds of listings per minute, flooding the moderation queue and making it impossible for human reviewers to process legitimate submissions. CWE-770 (Allocation of Resources Without Limits) and OWASP A05 (Security Misconfiguration) both apply. The cost is not just operational — spam that slips through moderation poisons the directory's data quality for every downstream user and search engine that indexes it.

## Severity rationale

High because uncapped submission rates enable queue flooding that incapacitates moderation, drives up infrastructure costs, and degrades directory data quality at scale.

## Remediation

Implement server-side rate limiting keyed by IP (and user ID when authenticated). Use an atomic increment in Redis so the counter survives across serverless cold starts:

```ts
// app/api/listings/submit/route.ts
import { Redis } from '@upstash/redis'

const redis = new Redis({ url: process.env.UPSTASH_REDIS_URL!, token: process.env.UPSTASH_REDIS_TOKEN! })

const ip = req.headers.get('x-forwarded-for') ?? 'unknown'
const key = `sub_rate:${ip}`
const count = await redis.incr(key)
if (count === 1) await redis.expire(key, 60) // 60-second window

if (count > 5) {
  await logSecurityEvent({ type: 'submission-rate-exceeded', ip, count })
  return Response.json({ error: 'Too many submissions — try again in a minute.' }, { status: 429 })
}
```

Send an admin alert (email or Slack webhook) when a single IP exceeds the threshold so patterns can be investigated and IPs permanently blocked if warranted.

## Detection

- **ID:** `rate-limiting-batch`
- **Severity:** `high`
- **What to look for:** Examine the submission API logic. Check for rate limiting that tracks submissions per IP or user. Look for logic that: (1) counts submissions in a time window (e.g., 60 seconds), (2) blocks further submissions if a threshold is exceeded (e.g., >5 per 60s), (3) logs or alerts admins about the abuse.
- **Pass criteria:** Enumerate all relevant code paths. Rate limiting is enforced server-side. More than 5 submissions per 60 seconds from the same IP or user triggers a temporary block (e.g., 10-minute cooldown) and logs the event for admin review. with at least 1 verified instance. On pass, report the count of validated fields vs. total fields.
- **Fail criteria:** No rate limiting, or rate limiting only on client-side, or no alert mechanism.
- **Skip (N/A) when:** The project doesn't allow rapid submissions (e.g., requires manual review for each submission).
- **Detail on fail:** `"A user can submit 100 listings in 1 second. No rate limiting. Server gets hammered with spam."` or `"Rate limit exists but is not enforced — server returns 429 but still processes the request."`
- **Remediation:** Implement server-side rate limiting:

  ```ts
  // Using a library like express-rate-limit or custom logic
  import { Redis } from '@upstash/redis'

  const redis = new Redis({
    url: process.env.REDIS_URL,
    token: process.env.REDIS_TOKEN
  })

  const RATE_LIMIT_WINDOW = 60 // seconds
  const RATE_LIMIT_MAX = 5 // max submissions

  export async function POST(req: Request) {
    const clientIp = req.headers.get('x-forwarded-for') || 'unknown'
    const key = `submission:${clientIp}`

    const count = await redis.incr(key)
    if (count === 1) {
      await redis.expire(key, RATE_LIMIT_WINDOW)
    }

    if (count > RATE_LIMIT_MAX) {
      // Alert admin
      await logSecurityEvent({
        type: 'rate-limit-exceeded',
        ip: clientIp,
        count,
        timestamp: new Date()
      })

      return Response.json(
        { error: 'Too many submissions. Please try again later.' },
        { status: 429 }
      )
    }

    // Proceed with submission
  }
  ```

## External references

- cwe CWE-770
- owasp A05

Taxons: injection-and-input-trust, cost-efficiency

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