Unbounded Consumption and Denial of Wallet
|
This content was generated with the assistance of AI and should be verified against the official documentation of each tool and framework before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Written on 2026-10-01 against the OWASP Top 10 for LLM Applications 2026 edition (LLM06 Unbounded Consumption), the OWASP Top
10 for Agentic Applications (ASI08 Cascading Failures), MITRE ATLAS (AML.T0029 and AML.T0024), NIST AI 600-1 (July 2024),
langchain 1.4.3 and Spring AI 2.0.1, as recorded in the version baseline. No
snippet or test command on this page was run against a live DocsAssistant, and no results are shown; the snippets are
illustrative and derived from the linked official documentation. The prices in the worked example are invented round numbers
chosen for easy arithmetic, not any provider’s price list.
An LLM endpoint is unusual among web services because the cost of a request is chosen by the caller. A request with a long prompt, a request that coaxes a long answer, or a request that makes an agent call tools in a loop can cost thousands of times more than a typical one, and the provider bills per token whether or not the caller is a legitimate user. The 2026 OWASP entry groups three consequences: denial of service (the model or its quota is exhausted for everyone), denial of wallet (the bill explodes) and model extraction (the attacker uses the API as an oracle to copy the model’s behaviour). The 2023 edition listed denial of service and model theft separately (see the edition mapping); the books by Wilson and Aryan treat them together as a resource problem, and this page follows that approach.
Attack
The DocsAssistant is a public chat widget that answers questions over this site’s documentation using an agent with a
search_docs tool, as modelled in Trust Boundaries and Threat
Modeling. Four patterns stress it, described here at the level needed to design defences, not as a recipe.
-
Token flooding. Many concurrent requests, or a few very large ones, consume the provider quota or the self-hosted GPU so that legitimate users get errors or timeouts. Output length is as exploitable as input length because generation is the slow and expensive part.
-
Context exhaustion. A single huge pasted document, or a conversation that keeps appending to the history, pushes the prompt towards the context window limit. Each turn then re-sends and re-bills the whole history, so cost grows with the length of the conversation, and quality degrades as the useful context is crowded out.
-
Recursive agent loops. The agent calls a tool, reads a result that invites another call, and repeats. The trigger can be a malicious document (indirect injection, see Prompt Injection) or simply a confused model; either way each iteration re-sends a growing transcript, so the loop cost is superlinear. OWASP’s agentic list classes the spread of such failures between steps or agents as ASI08 Cascading Failures.
-
Model extraction. A client sends a large, systematically chosen set of queries and trains its own model on the answers, or harvests the answers for a competitor’s dataset. MITRE ATLAS records the underlying path as exfiltration through the inference API (AML.T0024). Extraction is slow and looks like normal traffic, so per-account volume limits and anomaly detection are the main controls, together with terms of service that forbid it.
The diagram shows where each pattern enters and which control stops it.
Defend
The goal is a bounded system: for every request, user and key there is a number that the cost cannot exceed, enforced outside the model.
A worst-case cost bound
Cost is controlled only if it is computed. For one API key, over one billing window, the worst case is the product of the number of allowed model calls and the most expensive call allowed:
The symbols are defined as follows.
| Symbol | Meaning |
|---|---|
\( C_{\rm max} \) |
Worst-case spend for one key in the window, in currency units. |
\( R \) |
Maximum number of model calls the key can make in the window. Enforced by a rate limit or quota, and including every call an agent makes, not only the user-visible requests. |
\( n_{\rm in}^{\rm max} \) |
Maximum input tokens per call, enforced by an input size cap and the context window of the chosen model. |
\( n_{\rm out}^{\rm max} \) |
Maximum output tokens per call, enforced by the |
\( p_{\rm in} \), \( p_{\rm out} \) |
Price per input token and per output token. Output tokens are usually priced several times higher than input tokens, which is why capping output pays off. |
Worked example. Suppose the DocsAssistant allows each key \( R = 1000 \) calls per day, caps the prompt at \( n_{\rm in}^{\rm max} = 8000 \) tokens and the answer at \( n_{\rm out}^{\rm max} = 500 \) tokens, and suppose, with invented prices, that input costs 3 currency units per million tokens (\( p_{\rm in} = 3 \times 10^{-6} \)) and output costs 15 per million (\( p_{\rm out} = 15 \times 10^{-6} \)). Then:
One abusive key can therefore cost at most 31.5 units per day, and 200 such keys at most 6300. Without the caps there is no bound at all: the same key could send 100,000-token prompts and request 8,000-token answers. Two refinements follow. First, for an agent with up to \( L \) model calls per user request, \( R \) must count the calls, not the requests, and the input of later calls includes the earlier transcript, so \( n_{\rm in}^{\rm max} \) must be the true maximum prompt size at the last step. Second, \( C_{\rm max} \) is an input to budget settings: choose \( R \) and the token caps so that the sum over all keys stays below the monthly budget, instead of discovering the total from the invoice. Pair the calculation with real prices and volumes from Cost Management.
Python: LangChain 1.x
LangChain 1.x agents are built with create_agent, which accepts middleware. ModelCallLimitMiddleware caps model calls per
run and per thread, and ToolCallLimitMiddleware caps tool calls overall or for one tool, which stops a recursive loop even
when the model never decides to stop. The output length is capped on the model object. The snippet below is a minimal
sketch; the model name is a placeholder and the exact option names should be checked against the middleware reference before
use.
import os
from langchain.agents import create_agent
from langchain.agents.middleware import ModelCallLimitMiddleware, ToolCallLimitMiddleware
from langchain.chat_models import init_chat_model
from langgraph.checkpoint.memory import InMemorySaver
# The provider key comes from the environment, never from source code.
model = init_chat_model(
"openai:gpt-4.1-mini", # placeholder model identifier
max_tokens=500, # caps n_out_max for every call
)
agent = create_agent(
model,
tools=[search_docs], # the read-only tool from the threat model
checkpointer=InMemorySaver(),
middleware=[
ModelCallLimitMiddleware(thread_limit=20, run_limit=6, exit_behavior="end"),
ToolCallLimitMiddleware(tool_name="search_docs", run_limit=4),
],
)
This is derived from the LangChain built-in middleware documentation, and the local
LangChain middleware page shows the same limits in a worked, scripted example; the provider
SDK reads OPENAI_API_KEY from the environment. The middleware bounds the agent, not the caller: per-user and per-key
rate limits, the input size cap and the budget belong in the gateway or the web layer in front of it, because the agent never
sees the other requests.
Java: Spring AI 2.x and Spring Security
On the Java side the controls split across the web layer and the model call. Output is capped through the portable
ChatOptions (the maximum-tokens option), and the identity used for limits comes from Spring Security, so that the limit key
is the authenticated principal rather than the client IP address. Spring AI provides advisors that wrap the model call
(see Advisors), which is a good place for a custom advisor that rejects an oversized prompt
or records token usage per user; the rate limiter itself is usually a servlet filter or a gateway feature, using a
token-bucket library of the team’s choice. The snippet is conceptual: the limiter and the UsageMeter are application
classes that are not shown.
ChatClient client = ChatClient.builder(chatModel)
.defaultOptions(ChatOptions.builder().maxTokens(500).build()) // n_out_max
.defaultAdvisors(new TokenBudgetAdvisor(usageMeter)) // custom advisor, not shown
.build();
// In a filter ahead of the controller: one bucket per authenticated principal.
String key = SecurityContextHolder.getContext().getAuthentication().getName();
if (!limiter.tryConsume(key)) {
response.setStatus(429); // Too Many Requests
return;
}
The options builder is described in the Spring AI chat model reference and in
Chat Options; the authentication object is the standard Spring Security
SecurityContextHolder API. The TokenBudgetAdvisor is a name invented for this page, and whether the option builder and
advisor signatures match your Spring AI release should be verified against the 2.x reference.
Layered controls
The controls are most effective when each layer assumes the one before it failed.
-
Authentication first. Anonymous endpoints can only be limited per IP address, which is easy to evade. Require a login, or issue a per-visitor token with a small quota, so that cost can be attributed.
-
Per-user and per-key rate limits. Limit requests per minute for burst control and tokens per day for budget control; the second matters more for cost because one request can be large. Return a clear
429with a retry hint. -
Input size caps. Reject prompts, uploads and conversation histories above \( n_{\rm in}^{\rm max} \) before any model call, and summarise or truncate old history instead of re-sending it.
-
Output and step limits. Set
max_tokens, the maximum number of agent iterations, the maximum number of tool calls and a wall-clock timeout for the whole run. Stop with an explicit message, not a silent retry. -
Gateway budgets. An LLM gateway or proxy that holds the provider keys can enforce budgets per team, user or key, and switch to a cheaper model or refuse when the budget is spent. The application then never holds a key with unlimited spend. Most hosted providers also let you set project-level spend limits; set them as the last line of defence.
-
Cost anomaly alerts. Export token counts and cost per request, user and tool as metrics, and alert on spikes in tokens per minute, on a single key whose spend jumps, and on calls with unusual fan-out. See Metrics, Dashboards and Alerting.
-
Kill switch. Be able to disable a key, a tenant or a tool within minutes, and rehearse it.
-
Extraction resistance. Limit the volume per account, detect high-volume systematic querying, avoid returning log probabilities or other extra signals unless needed, and watermark or contractually restrict outputs where that is important to the business.
Test
Resource controls are testable with ordinary load tools and with LLM-specific scanners. The commands below are the ones the tools document; they were not run against a live DocsAssistant, so this page reports no results, and a load test against a metered provider should use a sandbox account with a hard spend cap.
-
garak includes probe families for denial-of-service style prompts (for example
dos) and for repeated or runaway output. List the available probes withpython -m garak --list_probesand run a family withpython -m garak --model_type rest -G rest.json --probes dos, whererest.jsondescribes the DocsAssistant endpoint. -
PyRIT orchestrates multi-turn attack conversations against a target; it can be used to script long conversations that test history growth and truncation, but it does not measure cost by itself.
-
promptfoo red-team configuration can include a
harmfulor resource plugin set inpromptfooconfig.yamland is run withnpx promptfoo@latest redteam run; use its assertions on latency and token usage to fail a run that exceeds the budget. -
Load and abuse tests. Use a conventional load tool to confirm that the rate limiter returns
429at the configured threshold, that an 8,000-token-plus prompt is rejected, and that an agent given a looping tool stops at the step limit. Record the observed maximum spend per key and compare it with \( C_{\rm max} \).
For the red-team process and how these tools fit together, see AI Red Teaming; for the generic guard layers, Guardrails; and for running such checks continuously, LLMSecOps.
Map
| OWASP LLM 2026 | OWASP Agentic | MITRE ATLAS | NIST AI 600-1 |
|---|---|---|---|
LLM06 Unbounded Consumption |
ASI08 Cascading Failures (runaway loops and cost amplification across steps and agents) |
AML.T0029 Denial of AI Service; AML.T0024 Exfiltration via AI Inference API (extraction) |
Information security; value chain and component integration (dependence on a metered third-party provider) |
The mapping is guidance for reporting, not an exact equivalence; the framework identifiers were taken from the version baseline, and the agentic names should be re-checked against the official PDF.
References
-
OWASP Top 10 for LLM Applications 2026: https://genai.owasp.org/resource/owasp-genai-llm-top-10-2026/
-
OWASP Top 10 for Agentic Applications: https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/
-
MITRE ATLAS: https://atlas.mitre.org/
-
NIST AI 600-1, Generative AI Profile: https://doi.org/10.6028/NIST.AI.600-1
-
LangChain, built-in middleware (call limits): https://docs.langchain.com/oss/python/langchain/middleware/built-in
-
Spring AI reference (chat client, advisors, chat options): https://docs.spring.io/spring-ai/reference/
-
promptfoo red teaming: https://www.promptfoo.dev/docs/red-team/
-
Tramer et al., Stealing Machine Learning Models via Prediction APIs (2016): https://arxiv.org/abs/1609.02943