An unmetered AI proxy route is a path to a five-figure bill
The concrete risk is a money risk, not a data leak. When your AI-built app calls an LLM provider, the call usually runs through a route on your own server, something like POST /api/chat or /api/generate, which forwards the prompt to OpenAI or Anthropic using a key only the server holds. That server-side proxy is the correct pattern: it keeps the provider key off the client. But the proxy itself becomes a metered resource you are paying for, one request at a time.
If that route answers anyone, anyone can spend your money. A script that loops requests through POST /api/generate with long prompts and a high max-tokens value turns your provider account into an open faucet. The caller pays nothing. You pay per token, and provider bills for abused proxy routes run into four and five figures before the dashboard alert even fires.
Here is the line between what is observable and what is only possible. What an outside visitor can observe is whether the route exists and whether it answers an unauthenticated request. What you cannot read from outside is whether a rate limit, a per-user budget, or a spend cap sits behind it. A route that responds to an anonymous call is a signal worth checking, not proof of abuse. The cost only materializes if nothing downstream stops the loop.
- The abused resource is provider tokens billed to you, charged per request regardless of who sent it.
- The proxy is meant to hide the key, which it does, while still happily spending it for any caller.
- This maps to the API Security category often labeled unrestricted resource consumption: the endpoint works, but nothing limits how often or how expensively it can be called.
Why AI-built apps ship this route wide open
Ask an AI builder for a chat feature and you get a working proxy route on the first try. The prompt round-trips, the UI streams an answer, the demo looks done. Rate limiting is not part of "make the chat work," so it does not appear in the generated code unless you ask for it by name.
Three defaults stack up. The generated route handler reads the request, calls the provider, returns the result, with no counter in between. There is often no authentication on it either, because adding login is a separate task from wiring the AI call, so the route answers before any user check. And max-tokens is frequently left high or unset, so each request can be as expensive as the model allows, which is exactly what an abuser dials up.
- Rate limiting is invisible from inside the app, because your own clicks never hit a limit during testing.
- The proxy is wired correctly to call the provider, which hides that it has no idea who is calling or how often.
- Fast deploy means the public URL exists before anyone reasons about cost under hostile traffic. This is the same generated-default pattern behind missing rate limits on a login endpoint; the cost ceiling here is just higher.
Check what your own AI route does from the outside
You can confirm this on your own app without attacking anything. Open DevTools, go to the Network tab, use the AI feature once, and watch which request carries your prompt. That request, often a POST to a path under /api, is your proxy route. Note its URL and method.
Then reason about two questions you can answer from the public surface. First, does the route answer without a session? In a fresh private window with no login, replay the same request to your own route a few times and watch the responses. Second, does anything slow you down? Send a short burst of requests and look at the status codes and headers.
A 429 in your test confirms a limit exists. It does not confirm the limit is set correctly, scoped to the right key, or that a determined caller cannot rotate IPs around it. Reading the door from outside has limits; the next section is where the real fix lives.
- Suspicious result: the route returns full AI completions to an unauthenticated caller, and a burst of requests all return 200 with no 429 and no Retry-After header. Nothing is throttling you.
- Safer result: the route requires a valid session, or a burst starts returning 429 Too Many Requests with a Retry-After header after a few calls. Something downstream is counting.
- Inspect the response for max-tokens behavior: if a deliberately long prompt produces an unbounded answer, the per-request cost ceiling is high. Run this only against your own app, with small request counts, so you are checking the door, not kicking it in.
Add limits, budgets, and a hard spend cap
Server-side enforcement is the only kind that counts. A limit checked in the browser is advisory; the caller controls the browser. Put every control on the server, in front of the provider call, so the request that would cost you money is the one that gets stopped.
Layer the controls. Per-user rate limits tie spending to an authenticated account, which means the route should require auth first so there is a user to limit. Per-IP rate limits catch unauthenticated or pre-login abuse, though IPs are cheap to rotate, so treat per-IP as a speed bump rather than a wall. A per-user request budget, a ceiling on calls or tokens per day, turns a runaway loop into a capped loss instead of an open one. Set a sane max-tokens on every provider call so a single request cannot be arbitrarily expensive. And set a hard spend cap and billing alerts in the provider dashboard itself, the backstop that fires when your application-level limits have a gap.
- Require authentication on the AI route, then apply a per-user limit; this gives you an account to throttle and to cut off.
- Add a per-IP limit for the unauthenticated edge, and a token or request budget per user per window as the real cost ceiling.
- Cap max-tokens per request, and set the provider-side spend limit and alert so a bug in your own limiting does not become an unbounded bill.
Rate limiting controls how often and how expensively your route can be called. It does not validate what the prompt contains or what the model is asked to do. A caller within the limit can still attempt prompt injection or coax unwanted output; that is a separate concern. See the note on prompt injection in AI-built apps for that side of the AI surface.
Where VibeCodeGuard fits, and what it does not prove
VibeCodeGuard's Launch Check works from the public surface, so what it can offer here is narrow and honest. If your app exposes a public AI proxy route that answers an unauthenticated request, the scan can flag that open route as a weak signal worth your attention before launch. An endpoint that returns an AI completion to an anonymous caller is exactly the kind of public-surface finding the scan surfaces with severity and fix direction.
That is a starting signal, not a verdict. Launch Check cannot read your code, so it cannot confirm whether a rate limit, a per-user budget, or a provider spend cap sits behind the route. Confirming that enforcement exists, that it is scoped correctly, and that the budget caps actually hold under abuse is code review and load reasoning, not a public scan.
Next step: find your AI proxy route in the Network tab, confirm it requires auth and applies a server-side per-user limit, then set a hard spend cap in your provider dashboard as the backstop. Run a focused Launch Check before sharing the URL publicly, so an open AI route shows up in your scan and not on your invoice.
A clean Launch Check does not prove your AI endpoint is rate limited. The scan sees whether the route is reachable and whether it answers without auth; it does not see your limiter, your budgets, or your provider settings. Treat an open, unauthenticated AI route as a launch blocker, and treat a clean result as one signal, not a guarantee that the cost is capped.
> launch check
Scan the public surface before launch.
Get severity, evidence, and practical fix guidance for the checks VibeCodeGuard can run from the outside.