Rate limiter that only exists when env vars are set
A spending or abuse guard that only exists when certain env vars are set.
Why it costs money
The code has a rate limiter, so a quick review says the endpoint is protected. What actually ships is protection only when two or three environment variables are present. A fresh deploy, a preview environment or a fork that never sets them serves every request with no quota.
That is the exact situation where abuse is most likely: a public URL that nobody is watching yet. Each request goes straight to the paid call, and the volume is set by whoever finds the endpoint.
Example
Flagged
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
import Replicate from "replicate";
const replicate = new Replicate();
const ratelimit = process.env.UPSTASH_REDIS_REST_URL
? new Ratelimit({ redis: Redis.fromEnv(), limiter: Ratelimit.slidingWindow(5, "1 d") })
: undefined;
export async function POST(req: Request) {
if (ratelimit) {
const ip = req.headers.get("x-forwarded-for") ?? "anonymous";
const { success } = await ratelimit.limit(ip);
if (!success) return new Response("Too many requests", { status: 429 });
}
const { prompt } = await req.json();
const output = await replicate.run("black-forest-labs/flux-schnell", { input: { prompt } });
return Response.json(output);
}Fixed
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
import Replicate from "replicate";
if (!process.env.UPSTASH_REDIS_REST_URL || !process.env.UPSTASH_REDIS_REST_TOKEN) {
throw new Error("Rate limiter is not configured. Set UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN.");
}
const replicate = new Replicate();
const ratelimit = new Ratelimit({ redis: Redis.fromEnv(), limiter: Ratelimit.slidingWindow(5, "1 d") });
export async function POST(req: Request) {
const ip = req.headers.get("x-forwarded-for") ?? "anonymous";
const { success } = await ratelimit.limit(ip);
if (!success) return new Response("Too many requests", { status: 429 });
const { prompt } = await req.json();
const output = await replicate.run("black-forest-labs/flux-schnell", { input: { prompt } });
return Response.json(output);
}How to fix it
Fail closed. If the limiter cannot be built in production, refuse to serve the paid route (a startup error or a 503) instead of quietly skipping the check.
For hand-rolled limits, give the setting a safe non-empty default so the check runs even when nobody configured it, and list the required variables in your deploy docs and one-click deploy config.
What the audit detects
- Library limiters (new Ratelimit, RateLimiter, rateLimit(...), slowapi and similar) whose construction is conditional (a ternary, && or if) on an environment variable, either directly or through a client such as a Redis handle that is itself built from env vars in another file.
- The request path must also use the limiter optionally, for example if (ratelimit) or ratelimit?.limit(). A limiter that is always constructed, or always applied, is not flagged.
- Hand-rolled guards (Redis INCR counters, row counts over a period, allowlist checks) wrapped in an if that is skipped when a config or env value is unset.
- Test, example, docs, scripts and migrations folders are skipped. It reports at most one library finding and three hand-rolled findings per repo.
Research
OWASP Top 10 for LLM Applications 2025: LLM10 Unbounded Consumption. A per-user quota that is missing from the default configuration gives no protection. Unbounded consumption depends on the deployed configuration, not on whether guard code exists.