Skip to content

fix(cloud-agent): rate limit stream tickets by IP - #5141

Closed
pandemicsyn wants to merge 1 commit into
mainfrom
fix/stream-ticket-ip-rate-limit
Closed

fix(cloud-agent): rate limit stream tickets by IP#5141
pandemicsyn wants to merge 1 commit into
mainfrom
fix/stream-ticket-ip-rate-limit

Conversation

@pandemicsyn

Copy link
Copy Markdown
Contributor

Summary

  • apply the existing @vercel/firewall limiter before stream-ticket authentication and ownership work
  • return 429 with Retry-After: 60 when the client-IP bucket is exhausted
  • add focused tests proving limited requests stop before authentication and allowed requests continue normally

Why

A single stale browser client generated roughly 2,000-2,400 unauthenticated stream-ticket requests per minute, peaking near 2,824/min. Normal per-IP traffic is approximately 1 request/min at p50, 2 at p90, 7 at p95, and 20 at p99.

Required Vercel setup

Create and publish a Firewall rule for the production Vercel project:

  • condition: @vercel/firewall
  • rate-limit ID: stream-ticket-ip
  • fixed window: 60 seconds
  • suggested initial limit: 120 requests
  • recommended rollout: Log first, then default 429 enforcement after validating matched traffic

The code uses Vercel's default client-IP bucket by intentionally omitting rateLimitKey.

Validation

  • pnpm --filter web test -- --runInBand src/app/api/cloud-agent-next/sessions/stream-ticket/route.test.ts
  • pnpm --filter web lint
  • pnpm --filter web typecheck
  • git diff --check

@kilo-code-bot

kilo-code-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (2 files)
  • apps/web/src/app/api/cloud-agent-next/sessions/stream-ticket/route.ts
  • apps/web/src/app/api/cloud-agent-next/sessions/stream-ticket/route.test.ts

Reviewed by kimi-k3 · Input: 441 · Output: 1.8K · Cached: 68.9K

Review guidance: REVIEW.md from base branch main

@pandemicsyn
pandemicsyn marked this pull request as draft August 7, 2026 17:35
*/
export async function POST(request: Request) {
try {
const { rateLimited } = await checkRateLimit(STREAM_TICKET_IP_RATE_LIMIT_ID, { request });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low, Security: checkRateLimit returns { rateLimited: false, error: 'not-found' } when the rate limit ID is not configured in the firewall (the SDK only does a console.warn). Since only rateLimited is destructured here, a missing or later deleted stream-ticket-ip rule silently disables the protection and the endpoint is back to the pre-incident state with no signal. Failing open is the right call for availability, but consider checking error === 'not-found' and reporting it to Sentry so a missing rule gets noticed. The throw path is already covered by the surrounding try/catch, so this is the only unobserved failure mode. Same pattern exists in the device-auth and native-admission call sites, so a shared helper or a follow-up there could make sense.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants