/api/chat/custom has no authentication and reads any user's custom-model API key via the service-role client
Summary
app/api/chat/custom/route.ts never calls getServerProfile() or checks the session. It creates a service-role Supabase client (lines ~20-23), loads a models row by customModelId taken from the request body (line ~28), and then uses that row's api_key and base_url to run completions with whatever messages the caller sends.
Impact
An unauthenticated caller who knows or guesses a models.id can run unlimited completions billed to another user's API key. Because base_url comes from the row, this is also a server-side request to a URL the model owner controls. There is no rate limiting on the route.
Suggested fix
Require auth and ownership before touching the model row:
const profile = await getServerProfile()
// then either query with the user's own client so RLS applies, or:
.eq("user_id", profile.user_id) // on the admin query, 404/403 if not foundIdeally build the client with the user's cookies (createServerClient + anon key) so the service-role key isn't needed in this route at all.
Context
Found by an automated full-codebase review (VibeAudit, Claude Fable 5.1); reasoning checked by hand, not exercised against a live instance. Full report: https://vibeaudit-amber.vercel.app/a/fx06chatbotui. Happy to be corrected.
Source: mckaywrigley/chatbot-ui