AI, LLM & agents
Partial9 minModel Denial of Service
An attacker drives an LLM system to consume resources far out of proportion to a single request — through long inputs, expensive generation, or recursive agent loops — degrading service or inflating cost, because no budget bounds how much a single request may consume.
Classification
- Domain
- AI, LLM & agents
- ROOTVIEL support
- Partial — Core is built; parts are not yet wired end-to-end.
- Analysis family
- FAM-RUNTIME
- CWE
- CWE-400, CWE-770
- OWASP
- LLM10
Definition
Model denial of service is the exhaustion of a system’s resources by making the model do disproportionate work for a single request. Unlike a classic flood of many requests, a single crafted request can be made expensive: a very long input, a prompt that induces very long generation, or an agentic task that fans out into many model calls and tool invocations. The result is service degradation for others, or runaway operational cost.
The defining quantity is amplification: how much work (tokens, calls, time, money) one request causes relative to a normal one. A system is exposed when there is no declared budget bounding that amplification — when a single request can consume an unbounded share of capacity. The vulnerability is the missing bound, and the finding is a number: the observed amplification measured against what the operator declared acceptable.
Context
This is relevant to any system where request cost is variable and attacker-influenceable: public endpoints billed per token, agents that loop and call tools, pipelines with recursive summarisation or retrieval, and any service whose capacity is shared across users. The agentic case is the most acute — an agent that decides its own next step can be induced to loop or fan out, multiplying a single request into many model calls.
- ▸Token-billed endpoints where a long input or output directly maps to cost.
- ▸Agents with loops or tool fan-out whose step count is not hard-bounded.
- ▸Shared-capacity services where one user’s amplified request starves others.
- ▸Recursive pipelines (summarise-of-summaries, multi-hop retrieval) with no depth cap.
Technical fundamentals
Model cost scales with tokens processed and generated, and for agents with the number of steps and tool calls. None of these is naturally bounded by the request itself: input length is attacker-controlled, output length depends on what the model decides to produce, and step count depends on the agent’s own control flow. Without an explicit budget, the only limit is the platform’s hard timeout — far above what a legitimate request needs.
The disciplined framing is a per-request consumption budget: a declared ceiling on tokens, calls, time, or cost that a single request may consume. With a budget, amplification becomes measurable — you compare an observed run’s consumption against the ceiling and the overage is the finding. Without a budget, or without an observed trace of a real run, there is nothing to measure against: you can neither confirm a problem nor rule one out, because the acceptable level was never defined.
- Amplification
- The work (tokens, calls, time, cost) one request causes relative to a normal one.
- Declared budget
- The operator-stated ceiling on what a single request may consume — the baseline the observed run is compared against.
- Observed trace
- A record of an actual run’s consumption; without it there is no measured amplification to judge.
Root cause
The defect is unbounded resource consumption: the system places no per-request ceiling on the work a single request may cause, so attacker-influenceable quantities (input length, generation length, agent step count) can be driven arbitrarily high. It is a missing-limit problem — the absence of a declared budget and the enforcement to hold a request to it — rather than a flaw in the model. The system implicitly assumes requests are cheap and well-behaved.
How the attack works, step by step
- 1The attacker identifies an attacker-influenceable cost driver: input size, output length, or agent step count.
- 2They craft a request that maximises that driver — a very long input, a prompt that induces long or repetitive generation, or a task that makes an agent loop or fan out tool calls.
- 3The system, with no per-request budget, processes the request to its expensive conclusion.
- 4Resources (compute, tokens, time, money) are consumed far out of proportion to one request.
- 5Repeated or concurrent, these requests degrade service for other users or inflate operational cost beyond what the operator expected.
Vulnerable example
A synthetic, illustrative agent loop with no per-request budget on steps, tokens, or cost:
synthetic — do not use; illustrates the flaw# UNSAFE: nothing bounds how much a single request may consume. def handle(request): state = init(request) # input length is unbounded while not state.done: # step count is unbounded step = model(state.prompt) # output length is unbounded state = apply(step) # may fan out into more tool calls return state.result # one request can cost arbitrarily much
Corrected example
The fix is to declare a per-request budget and enforce it: cap input size, generation length, step count, and total cost, and stop the request when any ceiling is reached. The declared numbers make amplification measurable — a run that approaches or exceeds the budget is observable rather than silent.
synthetic — declared per-request budget, enforced# SAFER: a declared budget bounds every request; overage is caught and measured. BUDGET = {"max_input_tokens": 8000, "max_output_tokens": 2000, "max_steps": 8, "max_cost": 0.50} # operator-declared ceilings def handle(request): enforce_input_limit(request, BUDGET) # reject oversized input up front state, spent = init(request), Spend() while not state.done and spent.within(BUDGET): # bounded steps / tokens / cost step = model(state.prompt, max_output_tokens=BUDGET["max_output_tokens"]) spent.add(step.cost) state = apply(step) if not spent.within(BUDGET): return stop_over_budget(spent, BUDGET) # the overage is the measurable finding return state.result
Limit of the fix: a budget bounds consumption but must be sized to real workloads — too tight breaks legitimate tasks, too loose leaves room to abuse. And the budget only means something if it is actually declared and enforced; an unset budget leaves the system in the UNKNOWN state, not a safe one.
Impact
- ▸Availability: amplified requests exhaust shared capacity and degrade or deny service to legitimate users.
- ▸Cost: token- or compute-billed systems incur runaway operational expense from a few expensive requests.
- ▸Cascading failure: in agentic systems, a looping or fanning request can exhaust downstream tool and API quotas too.
- ▸Unpredictability: without a budget, the worst-case cost of a single request is effectively unbounded.
Detection
Detection is a runtime measurement, not a code inspection: observe an actual run and compare its per-request consumption (tokens, calls, time, cost) against a declared budget. The amplification — how far the observed run exceeds the budget — is the finding, expressed as a number. This requires two inputs that are often missing: a declared budget to compare against, and an observed trace of a real run to measure. Where either is absent, there is nothing to compute.
How ROOTVIEL analyzes it
- Support level
- Partial — a consumption verifier compares observed runs against a declared budget; it depends on inputs that are often absent.
- Family
- Runtime: the signal comes from an observed run’s consumption, not from static code or a live red-team.
- Declared invariant
- “Per-request consumption must stay within the declared budget.”
A consumption verifier compares an observed run’s per-request amplification against a DECLARED budget. The finding is the number — how much the observed run consumed relative to the ceiling the operator declared — not a qualitative judgement. Both inputs are required: if there is no declared budget, or no observed trace of a real run, the verifier returns UNKNOWN, because there is neither a baseline to measure against nor a measurement to make. Support is partial precisely because the analysis is only as available as those two inputs.
Honest reporting: no declared budget ⇒ UNKNOWN (the acceptable level was never defined); no observed trace ⇒ UNKNOWN (nothing was measured). A finding exists only when an observed run exceeds a declared budget, and it is reported as the measured amplification, never as a generic “at risk” or a guarantee of safety.
Mitigation
Declare and enforce per-request budgets, and make consumption observable so abuse is measurable.
- ▸Preventive: declare a per-request budget (input tokens, output tokens, step count, cost, time) and enforce it in code; cap input size on intake and generation length per call; hard-bound agent loops and tool fan-out.
- ▸Preventive (platform): apply per-user rate and concurrency limits so amplified requests cannot monopolise shared capacity.
- ▸Detective: emit per-request consumption traces and compare them against the declared budget; alert on runs that approach or exceed it.
- ▸Corrective: abort a request that breaches its budget; apply back-pressure or throttling to an abusive source; adjust budgets as legitimate workloads evolve.
Verifying the mitigation
Verify by running representative and adversarial requests with the budget enforced and confirming that consumption stays within the declared ceilings and that over-budget requests are aborted with the overage recorded. Confirm both a declared budget and observed traces exist — without them the verifier can only return UNKNOWN. Re-check the budget against real workloads so it neither breaks legitimate tasks nor leaves abusable headroom.
A run within budget means “this observed run stayed within the declared ceiling”, not that every possible request will — an untested, more expensive request pattern may still exist.
Common mistakes
- ▸Relying on the platform’s hard timeout as the only limit, which sits far above any legitimate request’s cost.
- ▸Bounding input length but leaving generation length or agent step count unbounded.
- ▸Treating the absence of an observed over-budget run as safety, when no budget was declared (the state is UNKNOWN).
- ▸Setting a budget but never enforcing it in code, so it describes intent rather than a limit.
References
- [1]OWASP Top 10 for LLM Applications — LLM10 Unbounded Consumption
- [2]MITRE CWE-400 (Uncontrolled Resource Consumption)
- [3]MITRE CWE-770 (Allocation of Resources Without Limits or Throttling)
- [4]NIST AI 100-2 — Adversarial Machine Learning: taxonomy and terminology
References point to public, well-known technical sources for further study.
Related articles
