algorithmic-complexity-attacks
Skill0 starsChanged 3 months agoMITView on GitHub
This skill should be used when the user asks "is this regex safe", "what is ReDoS", "why does CPU spike on one request", "how do I safely accept uploads or XML", "is this parser safe against untrusted input", or is reviewing a regex over user input, handling compressed files, or responding to a ReDoS advisory.
In this repository29 other files it gives its agents.
- cache-stampedeSkill
- clocks-and-timeSkill
- code-hotspotsSkill
- connascenceSkill
- connection-poolingSkill
- consistent-hashingSkill
- coordinated-omissionSkill
- cyclomatic-complexitySkill
- database-queuesSkill
- dependency-confusionSkill
- expand-contractSkill
- floating-pointSkill
- hyrums-lawSkill
- index-designSkill
- isolation-anomaliesSkill
- keyset-paginationSkill
- metamorphic-testingSkill
- module-depthSkill
- mutation-testingSkill
- mvcc-and-bloatSkill
- parse-dont-validateSkill
- query-plansSkill
- queueing-and-utilizationSkill
- retry-amplificationSkill
- start-hereSkill
- tail-latencySkill
- unicode-pitfallsSkill
- universal-scalability-lawSkill
- write-amplificationSkill
The file
We split this skill into the rules it gives an agent
3 rules. About 0 are common, 3 are distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 87
Every regex over user input: linear-time engine, or input length capped?
- Distinctive: line 88
Every parser at a trust boundary: size cap, depth cap, entity expansion disabled?
- Distinctive: line 89
Every decompression: bounded output size and entry count?
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Algorithmic complexity attacks
Most code is fast on average and slow in the worst case. An algorithmic complexity attack is an attacker choosing the worst case deliberately. No memory corruption and no injection, just input costing 100,000 times more to process than to send.
The economics make it dangerous: a 50-byte request burning 30 seconds of CPU lets one laptop saturate a fleet, and it bypasses rate limiting because the request rate is trivially low.
1. ReDoS: catastrophic backtracking
Most engines (PCRE, Java, JavaScript, Python re, .NET, Ruby) backtrack. On a non-match they try every way to divide the input among quantifiers. When two quantifiers can match the same characters, the number of ways is exponential.
Vulnerable shapes:
(a+)+ nested quantifier
(a|a)* alternation with overlapping branches
(a|ab)* overlapping alternatives
\s*$ trailing quantifier with an anchor on a long whitespace run
^(\w+\s?)*$ the classic, since \w and \s? both consume the boundaryThe general rule: two quantified subexpressions that can match the same character.
re.match(r'^(\w+\s?)*$', 'a' * 30 + '!') # seconds
re.match(r'^(\w+\s?)*$', 'a' * 40 + '!') # hoursThe trailing ! matters. Blowup happens on the failed match, after every alternative is exhausted, so rejected input is what costs. That makes validation regexes the prime target.
Notable outages: Stack Overflow 2016 (a \s+$ trim regex), Cloudflare 2019 global outage (.*.*=.* in a WAF rule), plus a long series of npm CVEs.
Fixes in order
- Do not use a regex. Most vulnerable patterns validate email, URLs, or dates. Use a parser or a library. This eliminates the class.
- Use a linear-time engine. RE2 (Go's
regexp, plus bindings for Python, Node, Ruby) and Rust'sregexcrate guarantee linear time. They drop backreferences and lookaround, which are the features enabling blowup. - Bound input length before matching. Exponential blowup needs length, so a 256-character cap caps the damage.
- Rewrite the pattern so alternatives are disjoint:
(\w+\s?)*becomes\w+(\s\w+)*,(a|ab)*becomes(ab?)*. - Use atomic groups or possessive quantifiers (
(?>...),a++) where supported, which forbid backtracking into the group. Java and PCRE support this, JavaScript does not. - Set a timeout as a backstop. .NET supports regex timeouts, Java does not, so elsewhere run untrusted matching in a worker with a deadline.
Never accept a user-supplied regex. That is remote code execution against CPU. Where a search pattern must be offered, run it on RE2 with a length cap and timeout in an isolated worker.
Detection tools: redos-detector, recheck, safe-regex, Semgrep and CodeQL rules. Also grep for the shapes directly: nested quantifiers, (...)* containing |, and \s*$.
2. Hash flooding
Hash tables are O(1) average and O(n) worst case. Keys chosen to collide make every insert a linear scan, so inserting n keys costs O(n²). Send a JSON object with thousands of colliding keys. This took down PHP, Python, Ruby, Java, and Node simultaneously in the 2011 disclosure.
The fix is randomized hash seeds, now default in most runtimes:
- Python since 3.3, via
PYTHONHASHSEED. Do not pin it in production, which people do to make tests reproducible. - Rust
HashMapuses SipHash with a random seed. Swapping inFxHashMapfor speed removes the protection, which is fine for internal keys and not for user input. - Go maps randomize the seed.
- Java
HashMapsince 8 converts long collision chains to trees, giving O(log n) instead.
Still needing attention: hand-written hash maps, fast non-cryptographic hashes (FNV, xxHash) over attacker-controlled keys, and caches keyed by user input. Also cap the number of keys accepted in a parsed object.
3. Decompression and parser amplification
| Attack | Mechanism | Fix |
| Zip bomb | 42 KB expands to petabytes via nested or overlapping entries | Cap decompressed size and entry count, stream and abort past the limit. Never trust the declared size |
| Zip slip | Entry named ../../etc/cron.d/x escapes the extraction directory | Resolve each path after normalization and verify it stays under the target root |
| Billion laughs | XML entities expanding recursively, 10 entities x 10 levels = 10¹⁰ | Disable entity expansion (defusedxml, FEATURE_SECURE_PROCESSING) |
| XXE | External entities read local files or make requests | Disable external entities and DTDs |
| YAML bombs | Anchors and aliases expand exponentially, some loaders instantiate classes | yaml.safe_load always, plus a document size cap |
| JSON deep nesting | 100,000 nested arrays overflow a recursive parser's stack | Cap nesting depth and body size at the edge |
| Gzip bomb over HTTP | A Content-Encoding: gzip request body expanding enormously | Cap decompressed request size before decompressing |
The general rule: limit the output, not the input. A size check on the compressed payload proves nothing.
4. Other worst cases
- Quadratic string building.
s += xin a loop is O(n²) with immutable strings, and attacker-controlled length makes it a DoS. - Sorting with a user-supplied comparator. An inconsistent comparator can loop or throw, and predictable-pivot quicksort has an O(n²) adversarial input.
- Unbounded pagination or fan-out.
?limit=1000000is an attack requiring no cleverness. Cap server-side. - GraphQL query depth. Deep nesting multiplies into millions of resolver calls. Enforce depth and complexity limits.
- Timing side channels, the inverse problem: use constant-time comparison (
hmac.compare_digest,crypto.timingSafeEqual) for secrets, tokens, and signatures.
Review checklist
- Every regex over user input: linear-time engine, or input length capped?
- Every parser at a trust boundary: size cap, depth cap, entity expansion disabled?
- Every decompression: bounded output size and entry count?
- Any hash map keyed by user input using a non-randomized hash?
- Any pagination, fan-out, or query-depth parameter without a server-side maximum?
PYTHONHASHSEEDor equivalent unpinned in production?
A request costing 30 seconds of CPU and then timing out will be retried, turning one payload into three. See retry-amplification.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

