Rate Limiting
Rate limiting protects the chat system from abuse by restricting how many sessions a single IP can create.
Configuration
| Parameter | Value |
|---|---|
| Limit | 3 sessions per IP per hour |
| Storage | Cloudflare KV (THREAD_MAP) |
| Key pattern | rate:session:{ip} |
| TTL | 3600 seconds (1 hour, sliding) |
| Applied to | POST /session only |
How It Works
The rate limiter is a Hono middleware (sessionRateLimiter()) applied only to the session creation endpoint:
- Check for bypass (cookie or header) — if valid, skip rate limiting
- Extract IP from
cf-connecting-iporx-forwarded-for - Read current count from KV (
rate:session:{ip}) - If count >= 3, return
429 Rate limit exceeded - 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
Owner Bypass (HttpOnly Cookie)
For the site owner to bypass rate limiting:
- Activate:
POST /auth/bypasswith{ "token": "<RATE_LIMIT_BYPASS_TOKEN>" } - Worker sets
__bypass=1as an HttpOnly cookie (30-day expiry) - All subsequent requests with this cookie skip rate limiting
- Deactivate:
POST /auth/bypass/clear— sets cookie withMax-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_TOKENenvironment 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 availableif blocked
Turnstile Verification
All session creation requests require a valid Cloudflare Turnstile token:
- Client-side widget generates a token
- Token sent in
POST /sessionbody asturnstileToken - Worker verifies token with Cloudflare's siteverify API
- Invalid token returns
403 Turnstile verification failed