OWASP LLM10 (Unbounded Consumption) is the availability-and-wallet risk: an LLM endpoint that will happily process arbitrarily large or arbitrarily many requests can be driven into a denial-of-service or a denial-of-wallet — a five-figure token bill run up overnight — by a single abusive client.
Tier: Foundation · Workflow area: LLM attack surface & defense
Capping consumption is a Foundation control because the fixes are ordinary rate-limiting and quota discipline applied at the right layer. This playbook adds those limits to your own app and shows how to load-test that they actually bite.
Threat, briefly. An attacker (or a buggy client) sends huge inputs, requests enormous outputs, loops an agent, or floods the endpoint. Each call costs tokens and latency; unbounded, it exhausts your quota, spikes your bill, and starves legitimate users.
Cap input and output size per request. Reject oversized inputs before they reach the model and bound max_tokens to what the task genuinely needs.
MAX_INPUT_CHARS = 20_000
if len(user_input) > MAX_INPUT_CHARS:
raise ValueError("input too large") # fail fast, pre-model
response = client.messages.parse(
model="claude-opus-4-8",
max_tokens=1500, # bounded, not open-ended
thinking={"type": "adaptive"},
messages=[{"role": "user", "content": user_input}],
output_format=Answer,
)
Rate-limit per identity, not just per IP. Apply token-bucket limits keyed on the authenticated user or API key so one caller cannot monopolize the endpoint.
# sliding-window / token-bucket at the gateway; pseudocode
if not limiter.allow(key=user_id, cost=estimated_tokens):
return http_429("rate limit exceeded")
Set hard spend and concurrency quotas. Enforce a per-user and per-tenant daily token/spend ceiling and a max concurrent-request count, with alerts well before the ceiling.
Bound agent loops. Agentic features multiply cost per user turn. Cap tool-call iterations and total tokens per session so a goal-hijack or a loop can't run forever (this pairs with the excessive-agency playbook).
MAX_STEPS = 8
for step in range(MAX_STEPS):
... # break on completion
else:
abort("step budget exhausted")
Monitor and alert on consumption. Track tokens/spend per user over time; a sudden spike is your earliest abuse signal. Wire a threshold alert to on-call.
Load-test the limits on your local app. Fire a burst and an oversized-input flood at your local instance and confirm the limiter returns 429s and the spend meter plateaus instead of climbing.
# local target only
seq 1 500 | xargs -P50 -I{} curl -s -o /dev/null -w "%{http_code}\n" \
localhost:8080/ask -d '{"q":"hi"}' | sort | uniq -c
# expect a wall of 429 once the bucket drains
Put the token/spend ceiling below the number that would hurt, and alert at half of it. The goal is that abuse trips a cheap limit long before it trips your budget.
The defense holds when abuse is capped and observable. Run three checks against the local app:
Watch the spend meter throughout — the passing result is a flat line under load, not a climbing one. Keep these as automated checks so a removed limit fails CI.
max_tokens. Leaving output length open invites maximal-cost responses on every call. Bound it to the task.For client deployments the limits protect the client's budget and their SLA, so agree the per-tenant ceilings and burst limits with them up front and enforce them in the gateway that sits inside their environment. The load test runs against a client-approved staging instance, never production, using synthetic traffic. None of this requires sending client data anywhere — you are testing capacity and throttling, not content — so the entire exercise fits inside an air-gapped or on-prem setup. Hand the client a one-page worst-case-spend estimate and the limit values that cap it, which doubles as evidence of a resource-abuse control for their risk register.