Lion Cipher Lab
Defend an LLM Service Against Unbounded Consumption (Denial-of-Wallet)
- AI Security
- Advanced
- ~75 min
- Free · no account required
Why it matters
Put a language model behind an endpoint and you have quietly signed up for a bill that scales with whatever your callers send. That is the risk OWASP's 2025 Top 10 for LLM Applications lists as LLM10, Unbounded Consumption, and it is the one most teams discover from an invoice rather than a pentest. The dangerous part is that it needs NO jailbreak and no clever prompt -- an attacker (or a buggy client, or one enthusiastic user) simply exploits the fact that model cost and latency scale with tokens, and a naive service caps neither. There are several ways in and they compound. A GIANT CONTEXT:…
What you'll learn
- building a deterministic, OFFLINE cost model for an LLM service: a pure-Python fallback tokenizer (default, no download, no API key -- tiktoken is optional) plus cost = f(input_tokens, output_tokens) at a per-1k price and latency proportional to tokens, seeded so every learner reproduces the SAME numbers
- constructing an attack corpus that needs NO jailbreak -- a benign baseline, a giant-context request, an output-amplification request (a huge max_tokens), a 'sponge' input (a short-looking string that tokenizes into thousands of tokens via whitespace runs and rare unicode), and a request flood -- and MEASURING each one's cost and latency amplification factor vs the baseline
- naming the OWASP LLM10 (2025) Unbounded Consumption failure modes precisely: Denial-of-Wallet (unbounded BILL) is distinct from latency-DoS (unbounded WAIT), and output tokens dominate both cost and latency because they are generated one at a time
- installing a LAYERED, deny-by-default guard: a hard input-token cap that rejects over-long inputs BEFORE the model is called, an output-token cap the caller cannot raise, a sponge validator (reject absurd tokens/char ratios), a per-caller rate limit + concurrency cap, and a per-request compute ceiling
- the Denial-of-Wallet control specifically: a per-caller COST BUDGET / accounting meter that accrues spend and returns 429 when the quota would be exceeded -- and PROVING with a measured flood that a request/sec rate limit alone does not bound the bill
- re-measuring the SAME attack corpus through the guard so every class is bounded: rejected attacks cost $0 at the model, the served ones are clamped to a hard per-request ceiling, and the flood is throttled to the budget
- the judgment layer: why 'just add rate limiting' is insufficient (one request can be a cost bomb), the capability-vs-safety tradeoff a cap imposes, streaming + early-abort on budget exhaustion, and monitoring/alerting on cost anomalies -- mapped explicitly to OWASP LLM10
Steps in this lab
- The attack, in one breath (OWASP LLM10)
- Set up: offline, key-free, deterministic
- Build the cost model (costmodel.py)
- Build the attack corpus and MEASURE the amplification (attack.py)
- Why 'just add rate limiting' does not save you
- Build the layered, deny-by-default guard and re-measure (defend.py)
- Read the bounded numbers: which control did which job
- Judgment: Denial-of-Wallet vs latency-DoS, and the tradeoff you are choosing
- What you built -- and the one sentence to keep