# Pattern Exhaustion

> Systematic pattern exhaustion methodology. Load after finding any confirmed vulnerability to search for all instances of the same root cause pattern across the codebase.

- Skill: `purpleailab/pattern-exhaustion` (Agent Skill)
- Install (CLI): `npx skillmds@latest add purpleailab/pattern-exhaustion`
- Raw SKILL.md: https://api.skillmd.com/api/skills/purpleailab/pattern-exhaustion/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: purpleailab (https://skillmd.com/u/purpleailab)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/purpleailab/pattern-exhaustion

---


# Pattern Exhaustion

When you find a vulnerability, you have found a PATTERN. The same developer
who wrote one broken auth check likely wrote twenty. The same framework that
misses one guard has a systemic design gap. One root cause → multiple CVEs.

Real-world examples:
- Parse Server: 4 advisories from guard bypass patterns (prototype chain,
  falsy-value guard, protected-field bypass, complexity validator)
- zrok: 3 advisories from access control (symlink traversal, broken ownership,
  reflected XSS in callback)
- AVideo: 3 advisories from deployment config (SSRF, exposed installer,
  memcached session leak)

## The Exhaustion Loop

### 1. Classify the root cause

After confirming a vulnerability, classify its root cause pattern:

| Root Cause | Search Pattern |
|---|---|
| Missing auth/authz check | `grep -rn 'def handle_\|router\.\(get\|post\|put\|delete\)' \| grep -v 'auth\|permission\|require\|middleware'` |
| Unvalidated path parameter | `grep -rn 'req\.params\|request\.args\|ctx\.params' \| grep -v 'sanitize\|validate\|path\.resolve\|path\.normalize'` |
| SQL string interpolation | `grep -rn 'f"SELECT\|f"INSERT\|f"UPDATE\|query(.*\+.*\|query(.*\$\{' --include='*.py' --include='*.ts' --include='*.js'` |
| shell: true with user input | `grep -rn 'shell:\s*true\|shell=True\|os\.system\|subprocess\.call.*shell' --include='*.py' --include='*.ts' --include='*.js'` |
| Missing ownership validation | `grep -rn 'delete\|update\|modify' --include='*.py' --include='*.ts' \| grep -v 'owner\|author\|created_by\|user_id.*=='` |
| Default-open feature flag | `grep -rn 'enabled.*=.*false\|feature.*disabled\|trust.*=.*true' --include='*.ts' --include='*.py'` |
| Unsafe deserialization | `grep -rn 'pickle\.loads\|yaml\.load\|yaml\.unsafe\|unserialize\|JSON\.parse.*reviver\|eval(' --include='*.py' --include='*.js' --include='*.php'` |
| Type confusion in guard | `grep -rn 'typeof.*==\|instanceof\|is_array\|Array\.isArray' --include='*.ts' --include='*.js' --include='*.php'` |
| Missing CSRF/state check | `grep -rn 'state=\|csrf\|nonce' --include='*.py' --include='*.ts' \| grep -v 'verify\|validate\|check'` |
| Prototype pollution | `grep -rn 'Object\.assign\|merge(\|extend(\|deepMerge\|_.merge\|lodash' --include='*.js' --include='*.ts'` |

For semgrep (when installed):
```bash
# Generic: user input reaching dangerous sink without sanitization
semgrep --config auto --severity ERROR /workspace/target/src/ --sarif -o /workspace/exhaustion.sarif
kg_ingest_sarif("/workspace/exhaustion.sarif", "pattern-exhaustion")
```

### 2. Enumerate all instances

Run the search. For EACH hit:
1. Check: is this the SAME pattern as the confirmed vuln?
2. Check: is there a mitigation the original instance lacked?
3. Check: is this in test/fixture/example code? (reject if so)
4. If potentially exploitable, create a HYPOTHESIS node:

```
kg_add_node("hypothesis", "Missing ownership check in DELETE /api/v2/unaccess",
  props={"pattern": "missing_ownership_validation", "file": "api/routes.py",
  "line": 87, "related_finding": "<original_finding_id>",
  "key": "api/routes.py:handle_unaccess:missing_ownership"})
```

Link to the original vulnerability:
```
kg_add_edge(new_hypothesis_id, original_vuln_id, "chains_to", weight=0.3)
```

### 3. Prioritize by impact

Not all instances are equal. Prioritize:
- **Unauthenticated endpoints** over authenticated ones
- **Write/delete operations** over read-only
- **Production code paths** over admin/debug routes
- **External-facing** over internal-only

### 4. Verify each instance

Feed each top candidate to the verifier with a separate positive and baseline
command. Persist the ``validate_workspace_finding`` result beside the candidate;
only a result with ``validated=true`` becomes a separate operational finding.

### 5. Exhaustion criteria

Stop the pattern search when:
- All instances have been checked (grep returns no unchecked matches)
- Remaining instances have proper mitigations in place
- The pattern no longer applies (different framework version, different code path)
- You've hit 10+ findings from the same pattern (diminishing returns on signal)

Record the exhaustion state:
```
kg_add_node("hypothesis", "pattern exhaustion complete: missing_ownership",
  props={"pattern": "missing_ownership_validation", "status": "exhausted",
  "total_instances": 15, "confirmed": 4, "rejected": 11})
```

## Graph Structure

After exhaustion, the graph should show:
```
VULNERABILITY (original finding)
  ← chains_to ← FINDING (variant 1)
  ← chains_to ← FINDING (variant 2)
  ← chains_to ← FINDING (variant 3)
  ← chains_to ← HYPOTHESIS (rejected variant, status=rejected)
```

This makes it easy for the report generator to group related findings
under a single root cause narrative.

