Find the places in your own code where a small input buys a large amount of work or memory, and bound them — input-controlled loops and allocations, super-linear algorithms, catastrophic regex backtracking, decompression and expansion ratios, unbounded fan-out and retries, and missing backpressure. Use whenever writing or reviewing code that sizes anything from an untrusted value, and whenever a system falls over under load rather than under attack. Trigger on "ReDoS", "zip bomb", "billion la...
Scanned 9/3/2026
Install to Claude Code
npx -y skills add annatchijova/SKILLS --skill resource-exhaustion-review --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Resource Exhaustion Review?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/annatchijova-resource-exhaustion-review)More formats (shields.io, HTML) on the badges page.
---
name: resource-exhaustion-review
description: Find the places in your own code where a small input buys a large amount of work or memory, and bound them — input-controlled loops and allocations, super-linear algorithms, catastrophic regex backtracking, decompression and expansion ratios, unbounded fan-out and retries, and missing backpressure. Use whenever writing or reviewing code that sizes anything from an untrusted value, and whenever a system falls over under load rather than under attack. Trigger on "ReDoS", "zip bomb", "billion laughs", "OOM", "the worker ran out of memory", "it hangs on large input", "quadratic", "N+1", "timeout under load", "retry storm", "thundering herd", "rate limit", "backpressure", "pagination", "batch size", "how big can this get", "we never set a limit", or a request whose cost depends on a caller-supplied count, depth, or size. Availability is an invariant like any other. This skill hardens code you own — it bounds work and sheds load; it never plans or conducts availability attacks against any system.
---
# Resource Exhaustion Review
Correctness review asks whether the code produces the right answer. This asks
what it costs to make it try. Availability is an invariant — *bounded work per
request* — and it is the one invariant almost nobody writes down, so almost
nobody notices when a refactor deletes it.
The measure that matters is not "is this slow?" but the **asymmetry ratio**:
> **What does one request cost the caller, and what does it cost the system?**
A 40-byte request that allocates 4 GB has a ratio of 10⁸. That ratio is the
finding. It does not require a botnet to matter — a single retrying client, a
misconfigured integration, or a customer importing a genuinely large file will
find it first, which is why this is reliability work as much as security work.
Scope: this skill reviews and hardens systems you own or are authorized to
test. Load testing goes against your own environment with the owner's
agreement. It produces limits, budgets, and load-shedding — never traffic
aimed at anyone.
Composes with the library:
- **validate-at-the-boundary** — the limit belongs at the edge, with a clear
error, before the value sizes anything
- **secure-by-construction** — bounds are part of the contract, written before
the diff
- **oracle-driven-fuzzing** — a timeout or an allocation ceiling is an oracle;
fuzz for the input that hits it
- **concurrency-reasoning** — retry storms, thundering herds, and pool
starvation are concurrency failures wearing a capacity costume
- **sql-aggregation-not-materialization** — the database-specific case of
loading unbounded rows into memory
- **honest-degradation** — when shedding load, fail visibly; never return a
truncated answer as if it were complete
---
## Part 1 — Follow the untrusted number
Grep-equivalent for this class: find every place an attacker-influenced value
becomes a *size*, a *count*, a *depth*, or a *repetition*.
```
limit count size length depth page per_page batch n
range repeat timeout retries width height quality scale
```
For each, ask three questions in order:
1. **Is there a maximum?** Not a default — a *ceiling*, enforced at the
boundary, that a caller cannot exceed.
2. **Is the work linear in it?** Two nested loops over a caller-controlled list
turn 10× input into 100× work.
3. **Is the resource released?** Bounded per request is not bounded in
aggregate if a thousand requests each hold their bound simultaneously.
The classic sources of an untrusted number that reviewers miss: a length prefix
in a binary format (allocating the declared size before reading it), a
`Content-Length` used to preallocate, a JSON array's length, a page size, a
recursion depth in nested structures, a `count` in a batch endpoint, image
dimensions in a header, and a client-supplied timeout or retry count.
## Part 2 — The amplification families
| Family | Shape | Where it hides |
|---|---|---|
| **Allocate-before-verify** | trust a declared size and reserve it before the bytes arrive | binary parsers, upload handlers, RPC framing |
| **Expansion ratio** | small compressed input, huge expansion | gzip/zip/tar entries, XML entities, deeply nested JSON, image and video decoding, recursive templates |
| **Super-linear algorithm** | O(n²) or worse in caller-controlled n | nested loops, repeated string concatenation, `list.remove` in a loop, dedup by scan, join in application code |
| **Catastrophic backtracking** | nested quantifiers with overlapping alternatives, applied to caller input | validation regexes, log parsing, `User-Agent` matching, anything user-supplied compiled as a pattern |
| **Hash/collision degeneration** | worst-case buckets or cache keys chosen by input | hash maps keyed by raw input, dedup sets, cache namespaces |
| **Fan-out** | one request becomes many downstream calls | N+1 queries, per-item API calls, webhook broadcast, graph query depth |
| **Unbounded accumulation** | memory that grows and is never trimmed | in-memory caches without eviction, unbounded queues, per-connection buffers, log lines held for batching |
| **Retry amplification** | each layer retries the layer below | client × gateway × service × DB: 3 layers of 3 retries is 27× at the bottom |
| **Held resources** | scarce handles kept during slow work | a DB connection held across an HTTP call, a lock held during I/O, a thread per idle connection |
Two rules of thumb that catch most of it: **never allocate what you have not
yet received**, and **never let one request's work depend on a number the
request itself chose**.
## Part 3 — Bound it at four levels
A single limit is usually the wrong fix; work down this ladder and take every
rung that applies.
1. **Bound the input.** Maximum body size, maximum array length, maximum
nesting depth, maximum decompressed bytes (stream and count, aborting when
the ratio is exceeded — never decompress to memory to measure it), maximum
page size with a hard server-side cap. Reject at the boundary with a clear
error naming the limit.
2. **Bound the work.** A timeout and a memory ceiling per operation, enforced
where the work happens. Replace the super-linear algorithm where one exists;
for regex, prefer a real parser or a non-backtracking engine over patching
the pattern.
3. **Bound the concurrency.** Bounded pools and bounded queues, so the system
refuses rather than swallows. Apply backpressure: a full queue must reject,
not grow. Retries need jitter, a budget, and a circuit breaker; retry at one
layer only.
4. **Bound the blast radius.** Isolate expensive work in its own pool or worker
so it cannot starve cheap requests. Quota per tenant, not only globally —
one customer's import must not consume everyone's capacity. Shed load
deliberately, and say so honestly in the response (`honest-degradation`),
never by silently returning partial results.
Then **write the bound down** as a stated contract: "this endpoint accepts at
most 1 MB / 1 000 items / depth 32, and completes within 5 s". Undocumented
limits get removed by the next refactor; a limit in the contract gets a test.
## Part 4 — Prove the bound, then keep it
- **A limit without a test is a comment.** Write the test that sends
limit + 1 and asserts the rejection, and the test that sends the limit and
asserts success. `falsifiable-testing`: see it fail first.
- **Measure the ratio, do not estimate it.** Run the worst case you can
construct in your own environment and record input bytes → peak memory, CPU
time, and downstream calls. That measurement is the evidence in the report;
"could be exponential" is a hypothesis.
- **Fuzz for it.** Point `oracle-driven-fuzzing` at the parser with a timeout
and an RSS ceiling as the oracle. This class is unusually well suited to
automated search, because the oracle is trivial and needs no ground truth.
- **Alert on the ratio, not just on the outcome.** Requests whose cost exceeds
the budget are a detection (`detection-engineering`) and an early warning
that a limit was removed. Paging on the OOM is paging on the aftermath.
---
## Deliverable
```
## Resource review — <component>
| path | untrusted number | ratio (in → cost) | current bound | proposed bound |
Measured worst case: <input> → <peak memory / CPU / downstream calls>
Bounds added: input / work / concurrency / blast radius
Tests: limit+1 rejected, limit accepted, measured under the ceiling
Stated contract: <the documented limits>
Not reviewed: <paths not covered>
```
## Anti-patterns
- Treating this as a performance nicety and deferring it — availability is a
security property, and the ratio is the finding.
- Adding a rate limit and calling the class closed: rate limits divide the
cost, they do not bound one request's work.
- Setting a default instead of a ceiling, so a caller can pass a larger value.
- Decompressing fully in order to check the decompressed size.
- Retrying at every layer, multiplying load exactly when the system is
degraded.
- Enforcing a global quota only, so one tenant can consume the whole budget.
- Patching a catastrophic regex by adding another quantifier.
- Shedding load by returning a truncated result that looks complete.
- Removing a limit during a refactor because "no test covered it" — which is
the argument for the test, not for the removal.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!