Skip to main content
ZELSIS

Your SMS and AI bills are part of the attack surface

Most endpoints cost you almost nothing per call. Two kinds are different. A route that sends a verification code by SMS pays the SMS provider for every message, and a route that calls an LLM pays the model provider for every token. If either one can be called without a limit, whoever finds it decides how large your bill gets.

SMS pumping

A sign-up or login page sends a one-time code to whatever phone number it is given. Attackers point it at premium-rate numbers they get a share of the revenue from, and a script requests codes in a loop. Each message is cheap; the volume is not.

The best-known case is Twitter. In 2023 Elon Musk said the company was paying about $60 million a year for fake 2FA text messages that bots triggered. That is his figure, and it was not independently checked, but carriers and SMS providers describe the same pattern (Gigazine, citing Commsrisk).

Anyone can trigger a paid SMS
export async function POST(req: Request) {
  const { phone } = await req.json();
  await twilio.verify.v2.services(SID).verifications.create({ to: phone, channel: 'sms' });
  return Response.json({ sent: true });
}
Limited per number and per IP, CAPTCHA first
export async function POST(req: Request) {
  const { phone, captchaToken } = await req.json();
  if (!(await verifyTurnstile(captchaToken))) return new Response('captcha', { status: 400 });
  const ip = req.headers.get('x-forwarded-for') ?? 'unknown';
  const [byPhone, byIp] = await Promise.all([smsLimit.limit(`phone:${phone}`), smsLimit.limit(`ip:${ip}`)]);
  if (!byPhone.success || !byIp.success) return new Response('slow down', { status: 429 });
  await twilio.verify.v2.services(SID).verifications.create({ to: phone, channel: 'sms' });
  return Response.json({ sent: true });
}

Also turn on your provider's fraud controls (Twilio Verify has geo permissions and fraud guard) and block country codes you do not serve. A limit in code helps; a spend cap at the provider is the backstop.

An open LLM endpoint is a free LLM for everyone

The same shape shows up in AI features. A route takes a prompt, calls the model with your API key and streams the answer back. Without authentication and a rate limit, that route is a proxy to the model that you pay for. People do find these and use them, and long prompts with long answers are the expensive kind.

Who is asking, how often, and how much
export async function POST(req: Request) {
  const user = await requireUser(req);                    // no anonymous calls
  const { success } = await aiLimit.limit(user.id);       // per-user rate limit
  if (!success) return new Response('slow down', { status: 429 });
  const { prompt } = await req.json();
  return streamText({ model, prompt: prompt.slice(0, 4000), maxOutputTokens: 800 }).toTextStreamResponse();
}

Set a monthly budget or hard limit in the provider console as well. It will not stop abuse, but it caps how much a bad night can cost.

Retries multiply it

A retry loop with no wait between attempts turns one failed call into several paid ones, and when the provider is slow every client retries at once. Wait between attempts with exponential backoff and jitter, and stop retrying errors that will not go away.

What it could cost you

We do not put a dollar figure on this for you, because it depends on your prices and on how fast your endpoint answers. The arithmetic is simple: the price of one request times the requests that get through in a minute, times the minutes before someone notices. When Zelsis finds one of these endpoints, the dashboard shows a small calculator next to it; enter your own price and rate and it works out an hour and a day of abuse. The numbers stay in your browser.

How Zelsis catches this

  • XL-12An endpoint that sends SMS with no rate limit, CAPTCHA or cooldown
  • WAF-02A POST route that calls an LLM with no rate limit
  • LLM-COST-06An LLM call retried in a loop with no wait between attempts
  • RES-01Any outside call retried in a tight loop with no backoff

Check your own repo

Public repositories scan free. Paste a GitHub URL on the dashboard and every finding comes with the file, the line and a fix.

Scan a repository free