Generation endpoints have no rate limiting / concurrency guard — shared links are an unlimited generation capability

Author: hyojunlimCreated Sep 2, 2026Updated Sep 2, 2026

Summary

app/api/get-next-completion-stream-promise/route.ts (~line 204) accepts any messageId, loads the chat, and calls together.chat.completions.stream with max_tokens: 20000 and a 270s deadline. There is no auth, no per-IP or per-chat throttle, no daily budget, and no check that a generation is already in flight for that message. /api/create-chat additionally runs a vision call per request, and /api/generate-chat-title runs an LLM call for any chatId. Public share URLs are /share/v2/<messageId>, so every shared link doubles as a valid generation capability for the underlying chat.

Impact

A simple loop can burn the Together balance in hours (each generation is up to 20k tokens / 4.5 min), fill the DB with junk chats, and eat Vercel function time. Nothing in the app surfaces it until the invoice.

Suggested fix

Add a limiter (e.g. @upstash/ratelimit) keyed by IP and by chatId in all three routes, return 429 when exceeded, and add a concurrency guard so one message can only have one in-flight generation:

const { success } = await ratelimit.limit(`gen:${ip}`)
if (!success) return new Response("Too many requests", { status: 429 })

A daily global spend cap is a cheap extra safety net.

Context

Found by an automated full-codebase review (VibeAudit, Claude Fable 5.1). Full report: https://vibeaudit-amber.vercel.app/a/fx07llamacoder. Happy to be corrected if there's protection at the edge I can't see.