Skip to content

Rate Limiting

Rate limiting protects the chat system from abuse by restricting how many sessions a single IP can create.

Configuration

ParameterValue
Limit3 sessions per IP per hour
StorageCloudflare KV (THREAD_MAP)
Key patternrate:session:{ip}
TTL3600 seconds (1 hour, sliding)
Applied toPOST /session only

How It Works

The rate limiter is a Hono middleware (sessionRateLimiter()) applied only to the session creation endpoint:

  1. Check for bypass (cookie or header) — if valid, skip rate limiting
  2. Extract IP from cf-connecting-ip or x-forwarded-for
  3. Read current count from KV (rate:session:{ip})
  4. If count >= 3, return 429 Rate limit exceeded
  5. Otherwise, increment count and reset TTL

Race Condition — KV Is Not Atomic

The rate limiter performs a read → check → write sequence against Cloudflare KV. Because KV is eventually consistent, two concurrent POST /session requests from the same IP can both read the old count before either write lands, allowing up to ~2x the configured limit (6 sessions instead of 3).

Why this is acceptable:

  • The site is a personal portfolio with low traffic — concurrent session creation from a single IP is extremely unlikely in normal use.
  • The worst-case outcome is a visitor getting a few extra chatbot sessions, not a security breach or data loss.
  • Stricter enforcement would require Durable Objects (additional cost and complexity) or Cloudflare's built-in Rate Limiting product — both overkill for this use case.

For a production SaaS, use Durable Objects or Cloudflare Rate Limiting rules for atomic enforcement.

Bypass Mechanisms

For the site owner to bypass rate limiting:

  1. Activate: POST /auth/bypass with { "token": "<RATE_LIMIT_BYPASS_TOKEN>" }
  2. Worker sets __bypass=1 as an HttpOnly cookie (30-day expiry)
  3. All subsequent requests with this cookie skip rate limiting
  4. Deactivate: POST /auth/bypass/clear — sets cookie with Max-Age=0

Cookie attributes:

  • Production: HttpOnly; Secure; SameSite=None; Path=/
  • Local dev: HttpOnly; SameSite=Lax; Path=/

E2E Test Bypass (Header)

For automated testing and post-deploy verification:

  • Send header X-Bypass-Rate-Limit: <RATE_LIMIT_BYPASS_TOKEN>
  • Token must match the RATE_LIMIT_BYPASS_TOKEN environment variable
  • Used in Playwright E2E tests and deployment smoke tests

Block List

In addition to rate limiting, IPs and emails can be blocked entirely:

  • KV key block:ip:{ip} — blocks an IP address
  • KV key block:email:{email} — blocks an email address
  • Checked during session creation before rate limit check
  • Returns 403 Chat is not available if blocked

Turnstile Verification

All session creation requests require a valid Cloudflare Turnstile token:

  1. Client-side widget generates a token
  2. Token sent in POST /session body as turnstileToken
  3. Worker verifies token with Cloudflare's siteverify API
  4. Invalid token returns 403 Turnstile verification failed