IntentiAI

Rate limiter that only exists when env vars are set

A spending or abuse guard that only exists when certain env vars are set.

Rule guard-conditional-on-envCost audit

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.

Rate limiter that only exists when env vars are set: LLM cost rule | IntentiAI