Back to Technical Mode

Model Denial of Service

Because generating each token costs real compute and money, and because context windows can be enormous, an attacker who controls the size or structure of a prompt can drive a single request's cost and latency up by orders of magnitude, exhausting a shared rate/cost budget or degrading service for every other user.

Cost and latency scale with both input tokens and requested output tokens — an attacker controls both if the app doesn't cap them
Recursive or self-referential prompting ('keep expanding on your previous answer indefinitely') can turn one user request into an unbounded generation loop if there's no hard output cap
A shared rate limit or budget pool means one abusive user's oversized requests directly degrade every other user's experience and cost — this is a multi-tenant noisy-neighbor problem, not just a single-account issue
Model-side timeouts and max_tokens caps are necessary but not sufficient — the request-acceptance layer (before the model call even starts) needs its own input-size limits

Want this flow explained in detail as it plays?

POST/api/summarize

Headers

Host:app.flowground.dev
Content-Type:application/json

Body

Loading...

Quick-Insert Payloads

Defense in depth

  • Enforce a hard input size limit (character/token count) at the API boundary, before the request is ever sent to the model — reject oversized input outright rather than truncating silently
  • Always set an explicit, conservative max_tokens on the model call itself — never rely on the model's absolute maximum as the effective limit
  • Apply per-user and per-IP rate limiting and cost budgets specifically for LLM-backed endpoints, separate from your general API rate limits, since a single LLM call's cost varies so much more than a typical CRUD request

Vulnerable vs. Fixed

app.post("/api/summarize", async (req, res) => {
  const response = await llm.chat({
    messages: [{ role: "user", content: `Summarize this: ${req.body.document}` }], // no size check on document
    // no max_tokens set — defaults to the model's absolute maximum
  });
  res.json({ summary: response.text });
});