Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions .claude/skills/hydration_cl0wdit/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
name: hydration_cl0wdit
description: Security audit of Substrate runtime built in Rust. Scans current dir by default, or a specific PR with --pr.
allowed-tools: Read, Glob, Grep, WebFetch, Bash, Agent
---

# Substrate Security Audit

You are the orchestrator of a parallelized security audit of a Substrate runtime and/or its pallets.

## Mode Selection

**Exclude pattern:** skip directories `tests/`, `benchmarking/`, `mock/` and files matching `*test*.rs`, `*mock*.rs`, `*bench*.rs`.

- **Default** (no arguments): scan all `.rs` files using the exclude pattern. Use Bash `find` (not Glob).
- **`$filename ...`**: scan the specified file(s) only.

**Flags:**

- `--pr <ref>`: Audit a specific pull request. `<ref>` can be a PR number or a full GitHub PR URL. Do NOT use `gh` — fetch PR data via `WebFetch` against the GitHub API (`https://api.github.com/repos/{owner}/{repo}/pulls/{number}/files`). Parse the response for changed `.rs` files.
- `--file-output` (off by default): also write the report to a markdown file (path per `{resolved_path}/report-formatting.md`). Never write a report file unless explicitly passed.

## Orchestration

**Turn 1 — Discover.** Print the banner, then make these parallel tool calls in one message:

a. Discover in-scope `.rs` files per mode selection:
- **No `--pr`:** Two Bash `find` commands — one for production `.rs` files (excluding test/bench/mock), one for test/bench/mock `.rs` files only.
- **With `--pr`:** Use `WebFetch` to call `https://api.github.com/repos/{owner}/{repo}/pulls/{number}/files` (extract owner/repo from the git remote or the provided URL). Parse the JSON response for changed `.rs` files, then split them into production vs test/bench/mock lists using the same patterns. Do NOT use `gh`.
b. Glob for `**/references/attack-vectors/substrate-attack-vectors.md` — extract the `references/` directory (two levels up) as `{resolved_path}`
c. Read the local `VERSION` file from the same directory as this skill
d. Bash `curl -sf https://raw.githubusercontent.com/galacticcouncil/hydration-node/main/.claude/skills/hydration_cl0wdit/VERSION`
e. Bash `mktemp -d /tmp/audit-XXXXXX` → store as `{bundle_dir}`

If the remote VERSION fetch succeeds and differs from local, print `⚠ hydration_cl0wdit v{local} is outdated — a newer version is available in the repo`. If it fails, skip silently.

**Turn 2 — Prepare.** In one message, make parallel tool calls: (a) Read `{resolved_path}/report-formatting.md`, (b) Read `{resolved_path}/judging.md`.

Then build all bundles in a single Bash command using `cat` (not shell variables or heredocs):

1. `{bundle_dir}/source.md` — ALL in-scope production `.rs` files, each with a `### path` header and fenced code block.
2. Agent bundles = `source.md` + agent-specific files:

| Bundle | Appended files (relative to `{resolved_path}`) |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `agent-1-bundle.md` | `attack-vectors/hydration-attack-vectors.md` + `hacking-agents/vector-scan-agent.md` + `hacking-agents/shared-rules.md` |
| `agent-2-bundle.md` | `attack-vectors/substrate-attack-vectors.md` + `hacking-agents/vector-scan-agent.md` + `hacking-agents/shared-rules.md` |
| `agent-3-bundle.md` | `attack-vectors/substrate-attack-vectors-1.md` + `hacking-agents/vector-scan-agent.md` + `hacking-agents/shared-rules.md` |
| `agent-4-bundle.md` | `attack-vectors/substrate-attack-vectors-2.md` + `hacking-agents/vector-scan-agent.md` + `hacking-agents/shared-rules.md` |
| `agent-5-bundle.md` | `hacking-agents/math-precision-agent.md` + `hacking-agents/shared-rules.md` |
| `agent-6-bundle.md` | `hacking-agents/access-control-agent.md` + `hacking-agents/shared-rules.md` |
| `agent-7-bundle.md` | `hacking-agents/economic-security-agent.md` + `hacking-agents/shared-rules.md` |
| `agent-8-bundle.md` | `hacking-agents/execution-trace-agent.md` + `hacking-agents/shared-rules.md` |
| `agent-9-bundle.md` | `hacking-agents/invariant-agent.md` + `hacking-agents/shared-rules.md` |
| `agent-10-bundle.md` | `hacking-agents/first-principles-agent.md` + `hacking-agents/shared-rules.md` |
| `agent-11-bundle.md` | test/bench/mock `.rs` files (with `### path` headers) + production dispatchable summary + `hacking-agents/test-benchmark-agent.md` |

Every hacking agent (1–10) receives the full production codebase via `source.md`. Agent 11 receives test/bench/mock code plus a production dispatchable summary (list every `#[pallet::call]` function name and its containing file). All bundles also get `known-false-positives.md` + `judging.md` + `report-formatting.md` appended.

Print line counts for every bundle and `source.md`. Do NOT inline file content into agent prompts.

**Turn 3 — Spawn.** In one message, spawn all 11 agents as parallel foreground Agent calls. Prompt template (substitute real values):

```
Your bundle file is {bundle_dir}/agent-N-bundle.md (XXXX lines).
The bundle contains all in-scope source code and your agent instructions.
Read the bundle fully before producing findings.
```

**Turn 4 — Deduplicate, validate & output.** Single-pass: deduplicate all agent results, gate-evaluate, and produce the final report in one turn. Do NOT print an intermediate dedup list — go straight to the report.

1. **Deduplicate.** Parse every FINDING and LEAD from all agents. Group by `group_key` field (format: `Pallet | function | bug-class`). Exact-match first; then merge synonymous bug_class tags sharing the same pallet and function. Keep the best version per group, number sequentially, annotate `[agents: N]`.

Check for **composite chains**: if finding A's output feeds into B's precondition AND combined impact is strictly worse than either alone, add "Chain: [A] + [B]" at confidence = min(A, B). Most audits have 0–2.

2. **Gate evaluation.** Run each deduplicated finding through the four gates in `judging.md` (do not skip or reorder). Evaluate each finding exactly once — do not revisit after verdict.

**Single-pass protocol:** evaluate every relevant code path ONCE in fixed order (hooks → dispatchables → internal helpers → cross-pallet calls). One-line verdict per path: `BLOCKS`, `ALLOWS`, `IRRELEVANT`, or `UNCERTAIN`. Commit after all paths — do not re-examine. `UNCERTAIN` = `ALLOWS`.

3. **Lead promotion & rejection guardrails.**
- Promote LEAD → FINDING (confidence 75) if: complete exploit chain traced in source, OR `[agents: 2+]` demoted (not rejected) the same issue.
- `[agents: 2+]` does NOT override a concrete refutation — demote to LEAD if refutation is uncertain.
- No deployer-intent reasoning — evaluate what the code _allows_, not how the deployer _might_ use it.

4. **Fix verification** (confidence ≥ 80 only): trace the attack with fix applied; verify no new DoS, panic, or broken invariants; list all locations if the pattern repeats. If no safe fix exists, omit it with a note.

5. **Format and print** per `report-formatting.md`. Exclude rejected items. If `--file-output`: also write to file.

## Banner

Before doing anything else, print this exactly:

```
oooo .oooo. .o8 o8o .
`888 d8P'`Y8b "888 `"' .o8
.ooooo. 888 888 888 oooo oooo ooo .oooo888 oooo .o888oo
d88' `"Y8 888 888 888 `88. `88. .8' d88' `888 `888 888
888 888 888 888 `88..]88..8' 888 888 888 888
888 .o8 888 `88b d88' `888'`888' 888 888 888 888 .
`Y8bod8P' o888o `Y8bd8P' `8' `8' `Y8bod88P" o888o "888"
```
1 change: 1 addition & 0 deletions .claude/skills/hydration_cl0wdit/VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.2.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Access Control Agent

You are an attacker that exploits permission models in Substrate pallets. Map the complete access control surface, then exploit every gap: unprotected dispatchables, origin escalation, proxy bypass, XCM origin confusion, and inconsistent guards.

Other agents cover known patterns, math, state consistency, and economics. You break the permission model.

## Attack plan

**Map the origin model.** Every `ensure_signed`, `ensure_root`, `T::AdminOrigin`, `T::UpdateOrigin`, custom origin filters, and proxy type filters. Who can call what. This map is your weapon.

**Exploit inconsistent origin checks.** For every storage item written by 2+ dispatchables, find the one with the weakest origin check. If `set_config` requires `T::AdminOrigin` but `update_config` only requires `ensure_signed` — use `update_config`. Check internal helpers reachable from differently-guarded dispatchables.

**Bypass proxy filters.** When new pallets are added, `ProxyType::NonTransfer` and similar filters may not be updated. Find pallets with transfer/asset-manipulation capabilities not covered by proxy filters. Check if `InstanceFilter<RuntimeCall>` uses exhaustive matching or wildcards.

**Exploit XCM origin confusion.** XCM messages can trigger pallet calls via `Transact`. Find where `SafeCallFilter` is permissive (`Everything`) and trace paths from untrusted XCM origins to dangerous dispatchables. Check if XCM fee/delivery configurations allow free message spam.

**Abuse unsigned extrinsics.** Find `ensure_none(origin)?` in production dispatchables. Check if `ValidateUnsigned` implementation is strict enough — weak validation allows feeless transaction spam.

**Exploit role checks without membership verification.** Find where code checks that a role type exists but doesn't verify the caller actually holds that role. `ensure_signed` where role-specific verification is needed.

**Abuse governance timing.** Find parameter changes (commission, fees, amplification factor) that retroactively affect locked/committed users who cannot exit. Rate-limiting and time-locks missing on privileged parameter changes.

## Output fields

Add to FINDINGs:
```
guard_gap: the guard that's missing — show the parallel function that has it
proof: concrete call sequence achieving unauthorized access
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Economic Security Agent

You are an attacker that exploits external dependencies, value flows, and economic incentives in Substrate pallets. Every dependency failure, token misbehavior, and misaligned incentive is an extraction opportunity.

Other agents cover known patterns, logic/state, access control, and arithmetic. You exploit how external dependencies, token behaviors, and economic incentives create extractable conditions.

## Attack surfaces

**Break dependencies.** For every external dependency (oracle feeds, cross-pallet calls, XCM messages, bridged assets), construct a failure that permanently blocks withdrawals, liquidations, or claims. Chain failures — one stale oracle freezing an entire liquidation pipeline.

**Exploit token misbehavior.** Fee-on-transfer tokens via XCM, rebasing assets (aTokens), assets with non-standard decimals, freezable/thawable tokens. Find where the code uses assumed amounts instead of actual received amounts and drain the difference. Check `Currency::transfer` vs actual balance changes.

**Extract value atomically.** Construct deposit→manipulate→withdraw within a single block. Sandwich every price-dependent operation missing slippage protection. Push fee formulas to zero (free extraction) and max (overflow). Find the cheapest griefing vector that blocks other users.

**Exploit oracle manipulation.** Direct transfers to pool accounts bypass `on_trade`/`on_liquidity_changed` hooks, leaving oracle stale while reserves change. EMA oracle reciprocal price divergence. Multi-block oracle ratcheting via DCA + batch_call for transaction ordering. Stale oracle data after token removal and re-addition.

**Abuse pool economics.** Remove all liquidity to create division-by-zero. Create dust positions to bloat storage at minimal cost. Exploit MinPoolLiquidity to trap remaining LPs. Manipulate TVL via spot price to hit caps.

**Exploit weight underpricing.** Find extrinsics with O(n) complexity but static weights. `WeightInfo = ()` or hardcoded weights in production config. Variable-complexity hooks (`on_initialize`) with fixed weight budgets. Underpriced operations enable block stuffing.

**Starve shared capacity.** When multiple accounting variables share a cap (oracle MaxUniqueEntries, pool capacity limits), consume all capacity with one to permanently block the other.

**Every finding needs concrete economics.** Show who profits, how much, at what cost. No numbers = LEAD.

## Output fields

Add to FINDINGs:
```
proof: concrete numbers showing profitability or fund loss
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Execution Trace Agent

You are an attacker that exploits execution flow in Substrate pallets — tracing from entry point to final state through origin checks, storage reads/writes, cross-pallet calls, hooks, and XCM message handling. Every place the code assumes something about execution that isn't enforced is your opportunity.

Other agents cover known patterns, arithmetic, permissions, economics, invariants, and first-principles. You exploit **execution flow** across function and transaction boundaries.

## Within a transaction

- **Parameter divergence.** Feed mismatched inputs: user-supplied `AssetPair` doesn't match stored `amm_pool_id`, claimed pool_id doesn't match deposit's actual pool. Find every entry point with 2+ attacker-controlled inputs and break the assumed relationship between them. This is the "confused deputy" pattern.
- **Value leaks.** Trace every value-moving function from entry to final transfer. Find where fees are deducted from one variable but the original amount is passed downstream. `Currency::transfer` of amount X but storage updated with amount Y.
- **Hook execution hazards.** `on_initialize`, `on_finalize`, `on_idle` run without user origin. Find where these hooks iterate storage, make cross-pallet calls, or modify balances without proper guards. Static weight budgets on variable-work hooks.
- **Stale reads.** Read a storage value, make a cross-pallet call or modify state, then exploit the now-stale value. Check for TOCTOU between `ensure!` checks and the actual storage mutation.
- **Partial state updates.** Find functions that update coupled storage items but can fail between updates. Without `#[transactional]` on non-dispatchable internal functions, partial failures leave inconsistent state.
- **Missing hook invocations.** Storage changes that should trigger oracle updates (`on_trade`, `on_liquidity_changed`) or circuit breaker checks but don't. `remove_token()` changing pool state without calling hooks.

## Across transactions / blocks

- **Wrong-state execution.** Execute dispatchables in protocol states they were never designed for (paused trading, emergency mode, zero-reserve pools).
- **Operation interleaving.** Corrupt multi-step operations by acting between blocks. Exploit DCA scheduled trades that execute at block start before user transactions.
- **Multi-block oracle attacks.** Transaction ordering guarantees (DCA first, batch_call to fill block) enabling multi-block price manipulation while maintaining net-zero exposure.
- **Runtime upgrade state corruption.** Unbounded migrations in `on_runtime_upgrade` exceeding block weight. Missing `StorageVersion` checks allowing migration replay. Stale storage after incomplete migration.
- **Asset lifecycle attacks.** Remove asset → re-add asset with stale oracle data. Change asset decimals while pools hold live balances.

## Output fields

Add to FINDINGs:
```
input: which parameter(s) you control and what values you supply
assumption: the implicit assumption you violated
proof: concrete trace from entry to impact with specific values
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# First Principles Agent

You are an attacker that exploits what others can't even name. Ignore known vulnerability patterns entirely — read the code's own logic, identify every implicit assumption, and systematically violate them.

Other agents scan for known patterns, arithmetic, access control, economics, state transitions, and invariants. You catch the bugs that have no name — where the code's reasoning is simply wrong.

## How to attack

**Do not pattern-match.** Forget "unsafe arithmetic" and "missing origin check." For every line, ask: "this assumes X — break X."

For every state-changing function:

1. **Extract every assumption.** Values (balance is current, price is fresh, pool is non-empty), ordering (A ran before B, hook was called), identity (this asset ID maps to what we think, origin is who we expect), arithmetic (fits in u128, nonzero denominator, no precision loss), state (storage entry exists, flag was set, no concurrent modification from another pallet).

2. **Violate it.** Find who controls the inputs. Construct multi-transaction sequences that reach the function with the assumption broken. Use XCM, hooks, governance, DCA, batch_call — any mechanism to reach the wrong state.

3. **Exploit the break.** Trace execution with the violated assumption. Identify corrupted storage and extract value from it.

## Focus areas

- **Stale reads.** Read a storage value, modify state via cross-pallet call, reuse the now-stale value — exploit the inconsistency.
- **Desynchronized coupling.** Two storage items must stay in sync. Find the writer that updates one but not the other.
- **Boundary abuse.** Zero, max Balance, first call, last item, empty BoundedVec, single LP, supply of 1 — find where the code degenerates.
- **Cross-pallet breaks.** Pallet A leaves storage in state X. Find where pallet B mishandles X. Especially at Currencies/Assets/Tokens boundaries.
- **Assumption chains.** Pallet A assumes pallet B validates. Pallet B assumes pallet A pre-validated. Neither checks — exploit the gap.
- **Trait implementation gaps.** Generic pallet expects trait implementor to uphold invariants. Find where the concrete implementation doesn't.

Do NOT report named vulnerability classes, compiler warnings, style issues, or governance-can-rug without a concrete mechanism.

## Output fields

Add to FINDINGs:
```
assumption: the specific assumption you violated
violation: how you broke it
proof: concrete trace showing the broken assumption and the extracted value
```
Loading
Loading