fix(cloud-agent): rate limit stream tickets by IP - #5141
Conversation
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Files Reviewed (2 files)
Reviewed by kimi-k3 · Input: 441 · Output: 1.8K · Cached: 68.9K Review guidance: REVIEW.md from base branch |
| */ | ||
| export async function POST(request: Request) { | ||
| try { | ||
| const { rateLimited } = await checkRateLimit(STREAM_TICKET_IP_RATE_LIMIT_ID, { request }); |
There was a problem hiding this comment.
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.
Summary
@vercel/firewalllimiter before stream-ticket authentication and ownership work429withRetry-After: 60when the client-IP bucket is exhaustedWhy
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:
@vercel/firewallstream-ticket-ipThe 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.tspnpm --filter web lintpnpm --filter web typecheckgit diff --check