# cairo-auditor

Security audit of Cairo/Starknet code. Trigger on "audit", "check this contract", "review for security". Modes - default (full repo), deep (+ adversarial reasoning), or specific filenames.

- **Kind:** skill
- **Source:** https://github.com/keep-starknet-strange/starknet-skills
- **Page:** https://forefy.com/skills/47363c82-c93f-47fa-909b-0bf1ba626fc6
- **API (JSON + files):** https://forefy.com/api/asr/47363c82-c93f-47fa-909b-0bf1ba626fc6

---

## .claude-plugin

```

```

## .claude-plugin/plugin.json

```json

```

## README.md

<p align="center">
  <img alt="cairo-auditor hero" src="../assets/cairo-auditor-hero.svg" width="100%" />
</p>

# cairo-auditor

A security agent for Cairo/Starknet — findings in minutes, not weeks.

Built for:

- **Cairo devs** who want a security check before every commit
- **Security researchers** looking for fast wins before a manual review
- **Anyone** deploying on Starknet who wants an extra pair of eyes

Not a substitute for a formal audit — but the check you should never skip.

<p>
  <img alt="mode default" src="https://img.shields.io/badge/mode-default-0969da" />
  <img alt="mode deep" src="https://img.shields.io/badge/mode-deep-7c3aed" />
  <img alt="fp gate" src="https://img.shields.io/badge/false--positive-gated-2ea043" />
  <img alt="deterministic smoke" src="https://img.shields.io/badge/deterministic%20smoke-pass-2ea043" />
</p>

<!-- TODO: add demo GIF once recorded -->
<!-- ## Demo -->
<!-- ![Running cairo-auditor in terminal](assets/demo.gif) -->

## Install

**Claude Code CLI:**

```bash
git clone https://github.com/keep-starknet-strange/starknet-skills.git \
  && mkdir -p ~/.claude/commands/cairo-auditor \
  && cp -R starknet-skills/cairo-auditor/. ~/.claude/commands/cairo-auditor/
```

**Cursor (manual guidance only):**

```bash
git clone https://github.com/keep-starknet-strange/starknet-skills.git \
  && mkdir -p docs/cairo-auditor \
  && cp -R starknet-skills/cairo-auditor/references/. docs/cairo-auditor/
```

Cursor does not execute this package as a runnable `/cairo-auditor` command. Use Claude Code CLI or Plugin Marketplace for the executable orchestrator flow. In Cursor, treat these files as reference guidance only.
There is no official global `~/.cursor/skills` install path for this package.

**Claude Code Plugin Marketplace:**

```bash
/plugin marketplace add keep-starknet-strange/starknet-skills
/plugin install cairo-auditor@starknet-skills
```

**Update to latest:**

```bash
cd starknet-skills && git pull
# Claude Code CLI:
cp -R cairo-auditor/. ~/.claude/commands/cairo-auditor/
# Cursor docs refresh (manual guidance only):
cp -R cairo-auditor/references/. docs/cairo-auditor/
```

## Usage

```bash
# Scan the full repo (default — 4 parallel agents)
/cairo-auditor

# Full repo + adversarial reasoning agent (slower, more thorough)
/cairo-auditor deep

# Review specific file(s)
/cairo-auditor src/contracts/account.cairo
/cairo-auditor src/contracts/account.cairo src/contracts/factory.cairo

# Write report to a markdown file (terminal-only by default)
/cairo-auditor --file-output
```

### Deterministic local scan (no AI)

```bash
python3 scripts/quality/audit_local_repo.py \
  --repo-root /path/to/your/cairo-repo \
  --scan-id my-audit
```

## Example output

```text
[P0] 1. Ungated Upgrade Path
  NO_ACCESS_CONTROL_MUTATION · src/contracts/account.cairo:42 · Confidence: 92

  Description
  External upgrade() calls replace_class_syscall without caller gate.
  Any account can replace the contract class, leading to full takeover.

  Fix
  - fn upgrade(ref self: ContractState, new_class: ClassHash) {
  + fn upgrade(ref self: ContractState, new_class: ClassHash) {
  +     self.ownable.assert_only_owner();

  Required Tests
  - Unauthorized caller reverts on upgrade
  - Owner successfully upgrades and new class hash persists
```

## How it works

The skill orchestrates a **4-turn pipeline**:

1. **Discover** — find in-scope `.cairo` files, run deterministic preflight
2. **Prepare** — build 4 code bundles, each with a different attack-vector partition
3. **Spawn** — 4 parallel vector specialists (`model: sonnet`), optionally + 1 adversarial (`model: opus` in deep mode)
4. **Report** — merge, deduplicate by root cause, sort by confidence, emit findings

Each agent scans the full codebase against 30 attack vectors from its partition (120 total), applies a strict false-positive gate, and formats findings with exploit paths and fix diffs.

## Known limitations

**Codebase size.** Works best under ~5,000 lines of Cairo. Past that, triage accuracy and mid-bundle recall degrade. For large codebases, run per-module rather than everything at once.

**What AI misses.** AI catches pattern-based vulnerabilities reliably: missing access controls, CEI violations, unsafe upgrades, zero-address initialization. It struggles with: multi-transaction state setups, specification/invariant bugs, cross-protocol composability, game-theoretic attacks, and off-chain oracle assumptions. AI catches what humans forget to check. Humans catch what AI cannot reason about. You need both.

## Benchmarks

Deterministic scorecards are smoke/regression gates, not final independent proof.

| Suite | Cases | Precision | Recall | Scorecard |
| --- | ---: | ---: | ---: | --- |
| Core deterministic | 42 | 1.000 | 1.000 | [v0.2.0-cairo-auditor-benchmark.md](../evals/scorecards/v0.2.0-cairo-auditor-benchmark.md) |
| Real-world corpus | 42 | 1.000 | 1.000 | [v0.2.0-cairo-auditor-realworld-benchmark.md](../evals/scorecards/v0.2.0-cairo-auditor-realworld-benchmark.md) |

Additional quality signals:

- External triage: [v0.2.0-cairo-auditor-external-triage.md](../evals/scorecards/v0.2.0-cairo-auditor-external-triage.md)
- Manual gold: [v0.2.0-cairo-auditor-manual-19-gold-recall.md](../evals/scorecards/v0.2.0-cairo-auditor-manual-19-gold-recall.md)

## Structure

```text
cairo-auditor/
  SKILL.md                     # 4-turn orchestration contract
  agents/
    vector-scan.md             # vector specialist instructions
    adversarial.md             # adversarial specialist instructions
  references/
    attack-vectors/            # 120 vectors in 4 partitions
    vulnerability-db/          # 13 canonical vulnerability classes
    judging.md                 # FP gate + confidence scoring
    report-formatting.md       # finding template + priority mapping
    semgrep/                   # optional Semgrep auxiliary rules
  workflows/
    default.md                 # 4-agent pipeline reference
    deep.md                    # + adversarial agent details
```

## SKILL.md

---
name: cairo-auditor
description: Security audit of Cairo/Starknet code. Trigger on "audit", "check this contract", "review for security". Modes - default (full repo), deep (+ adversarial reasoning), or specific filenames.
allowed-tools: [Bash, Read, Glob, Grep, Task, Agent]
---

# Cairo/Starknet Security Audit

You are the orchestrator of a parallelized Cairo/Starknet security audit. Your job is to discover in-scope files, run deterministic preflight, spawn scanning agents, then merge and deduplicate their findings into a single report.

## Quick Start

- Default flow: [workflows/default.md](workflows/default.md)
- Deep flow: [workflows/deep.md](workflows/deep.md)
- Report schema: [references/report-formatting.md](references/report-formatting.md)

## When to Use

- Security review for Cairo/Starknet contracts before merge.
- Release-gate audits for account/session/upgrade critical paths.
- Triage of suspicious findings from CI, reviewers, or external reports.

## When NOT to Use

- Feature implementation tasks.
- Deployment-only ops.
- SDK/tutorial requests.

## Rationalizations to Reject

- "Tests passed, so it is secure."
- "This is normal in EVM, so Cairo is the same."
- "It needs admin privileges, so it is not a vulnerability."
- "We can ignore replay or nonce edges for now."

## Mode Selection

**Exclude pattern** (applies to all modes):

- Skip exact directory names via `find ... -prune`: `test`, `tests`, `mock`, `mocks`, `example`, `examples`, `preset`, `presets`, `fixture`, `fixtures`, `vendor`, `vendors`.
- Skip files matching: `*_test.cairo`, `*Test*.cairo`.

- **Default** (no arguments): scan all `.cairo` files in the repo using the exclude pattern.
- **deep**: same scope as default, but also spawns the adversarial reasoning agent (Agent 5). Use for thorough reviews. Slower and more costly.
- **`$filename ...`**: scan the specified file(s) only.

**Flags:**

- `--file-output` (off by default): also write the report to a markdown file. Without this flag, output goes to the terminal only.

## Orchestration

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

(a) Resolve and persist in-scope `.cairo` files to `/tmp/cairo-audit-files.txt` per mode selection:

```bash
find <repo-root> \
  \( -type d \( -name test -o -name tests -o -name mock -o -name mocks -o -name example -o -name examples -o -name fixture -o -name fixtures -o -name vendor -o -name vendors -o -name preset -o -name presets \) -prune \) \
  -o \( -type f -name "*.cairo" ! -name "*_test.cairo" ! -name "*Test*.cairo" -print \) \
  | sort > /tmp/cairo-audit-files.txt
cat /tmp/cairo-audit-files.txt
```

For **`$filename ...`** mode, do not run `find`. Instead, run:

```bash
REPO_ROOT=$(python3 -c 'import os,sys; print(os.path.realpath(sys.argv[1]))' "<repo-root>")
> /tmp/cairo-audit-files.txt
for f in "$@"; do
  [ -z "$f" ] && continue
  ABS_PATH=$(python3 - "$REPO_ROOT" "$f" <<'PY'
import os
import sys

repo_root, arg = sys.argv[1], sys.argv[2]
candidate = arg if os.path.isabs(arg) else os.path.join(repo_root, arg)
print(os.path.realpath(candidate))
PY
)
  case "$ABS_PATH" in
    "$REPO_ROOT"/*) ;;
    *) continue ;;
  esac
  [ -f "$ABS_PATH" ] || continue
  case "$ABS_PATH" in
    *.cairo) echo "$ABS_PATH" >> /tmp/cairo-audit-files.txt ;;
  esac
done
sort -u -o /tmp/cairo-audit-files.txt /tmp/cairo-audit-files.txt
cat /tmp/cairo-audit-files.txt
```

(b) Glob for `**/references/attack-vectors/attack-vectors-1.md` and resolve:

- `{refs_root}` = two levels up from the match (`.../references`)
- `{skill_root}` = three levels up from the match (skill directory that contains `SKILL.md`, `agents/`, `references/`, `VERSION`)

(c) If `scripts/quality/audit_local_repo.py` exists relative to the skill's repo root, run the deterministic preflight for full-repo modes only (default/deep). In `$filename ...` mode, skip preflight so the context stays scoped to the targeted files:

```bash
python3 scripts/quality/audit_local_repo.py --repo-root <repo-root> --scan-id preflight --output-dir /tmp
```

Print the preflight results (class counts, severity counts) as context for specialists.

**Turn 2 — Prepare.** In a single message, make three parallel tool calls:

(a) Read `{skill_root}/agents/vector-scan.md` — you will paste this full text into every agent prompt.

(b) Read `{refs_root}/report-formatting.md` — you will use this for the final report.

(c) Bash: create four per-agent bundle files (`/tmp/cairo-audit-agent-{1,2,3,4}-bundle.md`) in a **single command**. Each bundle concatenates:
  - **all** in-scope `.cairo` files (with `### path` headers and fenced code blocks),
  - `{refs_root}/judging.md`,
  - `{refs_root}/report-formatting.md`,
  - `{refs_root}/attack-vectors/attack-vectors-N.md` (one per agent — only the attack-vectors file differs).

Print line counts per bundle. Example command:

Before running this command, substitute placeholders (`{refs_root}`, `{repo-root}`) with the concrete paths resolved in Turn 1.

```bash
REFS="{refs_root}"
SRC="{repo-root}"
IN_SCOPE="/tmp/cairo-audit-files.txt"
set -euo pipefail

build_code_block() {
  while IFS= read -r f; do
    [ -z "$f" ] && continue
    REL=$(echo "$f" | sed "s|$SRC/||")
    echo "### $REL"
    echo '```cairo'
    cat "$f"
    echo '```'
    echo ""
  done < "$IN_SCOPE"
}

CODE=$(build_code_block)

for i in 1 2 3 4; do
  {
    echo "$CODE"
    echo "---"
    cat "$REFS/judging.md"
    echo "---"
    cat "$REFS/report-formatting.md"
    echo "---"
    cat "$REFS/attack-vectors/attack-vectors-$i.md"
  } > "/tmp/cairo-audit-agent-$i-bundle.md"
  echo "Bundle $i: $(wc -l < /tmp/cairo-audit-agent-$i-bundle.md) lines"
done
```

Do NOT read or inline any file content into agent prompts — the bundle files replace that entirely.

**Turn 3 — Spawn.** In a single message, spawn all agents as parallel foreground Agent tool calls (do NOT use `run_in_background`). Always spawn Agents 1–4. Only spawn Agent 5 when the mode is **deep**.

- **Agents 1–4** (vector scanning) — spawn with `model: "sonnet"`. Each agent prompt must contain the full text of `vector-scan.md` (read in Turn 2, paste into every prompt). After the instructions, add: `Your bundle file is /tmp/cairo-audit-agent-N-bundle.md (XXXX lines).` (substitute the real line count). Include the deterministic preflight results if available so agents have extra context.

- **Agent 5** (adversarial reasoning, **deep** mode only) — spawn with `model: "opus"`. The prompt must instruct it to:
  1. Read `{skill_root}/agents/adversarial.md` for its full instructions.
  2. Read `{refs_root}/judging.md` and `{refs_root}/report-formatting.md`.
  3. Read `/tmp/cairo-audit-files.txt` to obtain in-scope paths, then read only those `.cairo` files directly (not via bundle).
  4. Reason freely — no attack vector reference. Look for logic errors, unsafe interactions, access control gaps, economic exploits, multi-step cross-function chains.
  5. Apply FP gate to each finding immediately.
  6. Format findings per report-formatting.md.

**Turn 4 — Report.** Merge all agent results:

1. Deduplicate by root cause (keep the higher-confidence version, merge broader attack path details).
2. Sort by confidence highest-first.
3. Re-number sequentially.
4. Insert the **Below Confidence Threshold** separator row at confidence < 75.
5. Print findings directly — do not re-draft or re-describe them.
6. Add scope table and findings index table per report-formatting.md.
7. Add the disclaimer.

If `--file-output` is set, write the report to `{repo-root}/security-review-{timestamp}.md` and print the path.

## Banner

Before doing anything else, print this exactly:

```text

 ██████╗ █████╗ ██╗██████╗  ██████╗      █████╗ ██╗   ██╗██████╗ ██╗████████╗ ██████╗ ██████╗
██╔════╝██╔══██╗██║██╔══██╗██╔═══██╗    ██╔══██╗██║   ██║██╔══██╗██║╚══██╔══╝██╔═══██╗██╔══██╗
██║     ███████║██║██████╔╝██║   ██║    ███████║██║   ██║██║  ██║██║   ██║   ██║   ██║██████╔╝
██║     ██╔══██║██║██╔══██╗██║   ██║    ██╔══██║██║   ██║██║  ██║██║   ██║   ██║   ██║██╔══██╗
╚██████╗██║  ██║██║██║  ██║╚██████╔╝    ██║  ██║╚██████╔╝██████╔╝██║   ██║   ╚██████╔╝██║  ██║
 ╚═════╝╚═╝  ╚═╝╚═╝╚═╝  ╚═╝ ╚═════╝     ╚═╝  ╚═╝ ╚═════╝ ╚═════╝ ╚═╝   ╚═╝    ╚═════╝ ╚═╝  ╚═╝

```

## Version Check

After printing the banner, run two parallel tool calls: (a) Read the local `VERSION` file from the same directory as this skill, (b) Bash `curl -sf --connect-timeout 5 --max-time 10 https://raw.githubusercontent.com/keep-starknet-strange/starknet-skills/main/cairo-auditor/VERSION`. If the remote fetch succeeds and the versions differ, print:

> You are not using the latest version. Run `/plugin marketplace update keep-starknet-strange/starknet-skills` for best security coverage.

Then continue normally. If the fetch fails (offline, timeout), skip silently.

Use this command for the remote check:

```bash
curl -sf --connect-timeout 5 --max-time 10 https://raw.githubusercontent.com/keep-starknet-strange/starknet-skills/main/cairo-auditor/VERSION
```

## Limitations

- Works best on codebases under **5,000 lines** of Cairo. Past that, triage accuracy and mid-bundle recall degrade.
- For large codebases, run per-module by passing explicit file arguments (`$filename ...`) rather than full-repo.
- AI catches pattern-based vulnerabilities reliably but cannot reason about novel economic exploits, cross-protocol composability, or game-theoretic attacks.
- Not a substitute for a formal audit — but the check you should never skip.

## Reporting Contract

Each finding must include:

- `class_id`
- `severity` (Critical / High / Medium / Low)
- `confidence` score (0–100)
- `entry_point` (file:line)
- `attack_path` (concrete caller -> function -> state -> impact)
- `guard_analysis` (what guards exist, why they fail)
- `recommended_fix` (diff block for confidence >= 75)
- `required_tests` (regression + guard tests)

## Evidence Priority

1. `references/vulnerability-db/`
2. `references/attack-vectors/`
3. `../datasets/normalized/findings/`
4. `../datasets/distilled/vuln-cards/`
5. `../evals/cases/`

## Output Rules

- Report only findings that pass FP gate.
- Findings with confidence `<75` may be listed as low-confidence notes without a fix block.
- Do not report: style/naming issues, gas optimizations, missing events without security impact, generic centralization notes without exploit path, theoretical attacks requiring compromised sequencer.

## VERSION

```

```

## agents

```

```

## agents/adversarial.md

# Adversarial Specialist

Construct realistic exploit paths that cross function and contract boundaries.

## Focus Areas

- Multi-step call chains (parent -> helper -> external interaction -> late state mutation).
- Trust-chain composition (owner -> manager -> allocator -> adapter).
- Session/account validation-execute interplay.
- Upgrade/admin takeover paths and failure modes.

## Required Output

For each candidate:

- attacker capability assumptions,
- exact reachable path,
- guard bypass analysis,
- concrete impact,
- confidence score per `../references/judging.md`.

Drop findings that cannot produce a concrete path to impact.

## agents/vector-scan.md

# Vector Scan Specialist

You are a Cairo/Starknet security auditor scanning one assigned attack-vector partition against the full in-scope code bundle.

## Critical Output Rule

Return findings only in your final response. Do not emit draft findings during analysis.

## Workflow

1. Read your assigned bundle in parallel 1000-line chunks on the first turn.
2. Do triage for every vector: `Skip`, `Borderline`, `Survive`.
3. Deep-check only surviving vectors and run the FP gate from `../references/judging.md`.
4. Format all surviving findings using `../references/report-formatting.md`.
5. If multiple findings survive, run one composability pass before final output.

## Bundle Reading Rule

- Read in parallel chunk calls (`offset`, `limit=1000`) until full bundle coverage.
- Do not read unbounded ranges.
- After initial bundle reads, do not read unrelated files unless explicitly needed for unresolved ambiguity.

## Triage Output Contract

Every vector must be in exactly one bucket.

- `Skip`: named construct and underlying exploit concept are absent.
- `Borderline`: named construct absent but exploit concept could appear via equivalent mechanism.
- `Survive`: construct or exploit concept is clearly present in code.

Triage format:

- `Skip: Vx, Vy, ...`
- `Borderline: Va, Vb, ...`
- `Survive: Vm, Vn, ...`
- `Total: N classified`

For each `Borderline`, keep one sentence: specific function + why concept can still manifest.

## Deep Pass Rules

Only process `Survive` vectors.

Use this one-line structure per vector before final formatted findings:

`Vxx` means the numbered vector from your assigned `attack-vectors-*.md` partition.

`V15 | path: entry() -> helper() -> sink() | guard: none | verdict: CONFIRM [85]`

`V22 | path: set_config() -> write() | guard: assert_only_owner | verdict: DROP (FP gate 3: guarded)`

Required checks per vector:

1. Trace concrete caller -> entrypoint -> state change -> impact path.
2. Confirm attacker reachability (role/caller/modifier checks).
3. Confirm no existing guard blocks exploit.

Budget:

- These budgets apply to the deep-pass one-liners in this section only.
- Full finding details are emitted later via `../references/report-formatting.md`.
- DROP vectors: <=1 line each.
- CONFIRM vectors: <=3 lines each before final formatted finding block.

## Composability Check

If 2+ findings survive, test whether findings compound (for example, auth weakness + arbitrary call = stronger impact). Add compound note in the higher-confidence finding description.

## Hard Stop

After deep pass + composability check:

- Do not rescan dropped vectors.
- Do not scan outside your assigned vector partition.
- Return final formatted findings or `No findings.`

## Scope Constraints

- Security findings only.
- No style-only, naming-only, or gas-only notes.
- No duplicate root causes across emitted findings.

## references

```

```

## references/README.md

# References Index

- Vulnerability classes: `vulnerability-db/README.md`
- Attack vectors (4 partitions): `attack-vectors/`
- Semgrep auxiliary rules: `semgrep/README.md`
- Judging/FP gate: `judging.md`
- Output contract: `report-formatting.md`
- Imported audit corpus notes: `audit-findings/README.md`
- Release gate checklist: `checklists/release-gate.md`

## references/attack-vectors

```

```

## references/attack-vectors/attack-vectors-1.md

# Cairo Attack Vectors (1/4): Access Control + Upgradeability

**1. Immediate upgrade without delay**
- **D:** privileged `upgrade` path calls `replace_class_syscall` or `upgradeable.upgrade` in one transaction with no schedule/execute split.
- **FP:** explicit timelock state with pending hash and `now >= scheduled + delay` check.

**2. Missing class-hash non-zero guard (direct syscall path)**
- **D:** direct `replace_class_syscall(new_class_hash)` without `is_non_zero` guard.
- **FP:** local guard exists or call goes through OZ `UpgradeableComponent` internal guard.

**3. Constructor critical role without non-zero guard**
- **D:** constructor writes `owner/admin/upgrade/governor` directly with no non-zero validation.
- **FP:** explicit constructor guard or trusted initializer with proven internal non-zero check.

**4. Irrevocable privileged role**
- **D:** privileged role seeded at deploy time with no reachable rotate/revoke path.
- **FP:** exposed ownership/role rotation path exists (for example Ownable transfer or AccessControl grant/revoke).

**5. One-shot registration of critical dependency**
- **D:** `register_*` write-once gate sets critical dependency and no recovery setter exists (ForgeYields-style risk).
- **FP:** documented immutable dependency or owner/governance recovery path exists.

**6. Ungated privileged mutation**
- **D:** external `set_*`, `register_*`, `upgrade*`, or pause path mutates privileged state without caller gate.
- **FP:** strict access check in-path (`assert_only_*`, role check, or explicit caller assertion).

**7. Caller alias bypass in access checks**
- **D:** caller stored in alias and compared to wrong role slot or stale authority field.
- **FP:** alias resolves to correct authority source tied to the same function path.

**8. External ABI mutation hidden behind helper**
- **D:** externally callable function delegates to helper that mutates privileged state; entrypoint lacks auth.
- **FP:** helper or parent enforces strict auth with no bypass path.

**9. Upgrade path reachable from broad role**
- **D:** upgrade guarded by overly broad role set (for example generic admin role with many holders).
- **FP:** dedicated upgrade role with constrained grant/revoke controls.

**10. Upgrade initializer omission**
- **D:** class replacement leaves migration state uninitialized, enabling unsafe defaults.
- **FP:** migration is atomically executed or new class is migration-free by design and documented.

**11. Mixed ownership models with orphan authority**
- **D:** contract uses both Ownable and AccessControl; privileged paths depend on stale model.
- **FP:** single authority model or explicit synchronization between models.

**12. Emergency controls not actually privileged**
- **D:** `pause`, `unpause`, or `emergency_*` paths callable through weak predicate.
- **FP:** dedicated emergency role, explicit gate, and no user-facing bypass.

**13. Timelock bypass through alternate upgrade entrypoint**
- **D:** one upgrade path enforces delay while another admin path upgrades immediately.
- **FP:** all upgrade routes converge on a shared timelock gate.

**14. Role-admin misconfiguration enables privilege escalation**
- **D:** `set_role_admin` ties sensitive role admin to itself or user-controlled role without constraints.
- **FP:** role hierarchy maps to governance/owner-only admin chain.

**15. Upgrade auth delegated to mutable external contract**
- **D:** upgrade permission depends on external contract state that can be swapped or desynced.
- **FP:** external dependency is immutable or integrity-checked per call.

**16. Initializer re-entry / double-init path**
- **D:** initializer callable more than once or callable post-deploy by non-trusted party.
- **FP:** initialized flag and strict deploy-time only gate prevent re-entry.

**17. Factory dependency validation missing on one constructor branch**
- **D:** multi-branch init validates non-zero dependencies on one branch but not another.
- **FP:** all constructor/init branches apply identical dependency checks.

**18. Role revocation impossible after bootstrap**
- **D:** privileged role granted in constructor but no external revoke flow (common mis-embed of AccessControl).
- **FP:** role management ABI (`grant/revoke/renounce`) is exposed and reachable.

**19. Pause bypass via equivalent state-mutating function**
- **D:** deposits/transfers are paused, but equivalent state-changing route remains open.
- **FP:** pause invariant enforced at shared internal gate for all equivalent routes.

**20. Admin transfer lockout edge**
- **D:** admin transfer path allows zero/self/inconsistent target and can lock governance flows.
- **FP:** transfer validates target and preserves recoverability guarantees.

**81. Mixed authority drift between Ownable and AccessControl**
- **D:** one privileged path checks Ownable while another checks role state, enabling desynced authority.
- **FP:** single authority model or explicit sync invariant across both models.

**82. Mutable timelock-delay downgrade**
- **D:** governance can set timelock delay to zero (or near-zero) and immediately execute privileged action.
- **FP:** delay changes are themselves timelocked with minimum floor enforcement.

**83. Schedule cancellation by lower privilege**
- **D:** pending upgrade/action can be canceled by role that cannot schedule/execute it.
- **FP:** cancellation privilege is equal or stricter than scheduling privilege.

**84. Pending upgrade overwrite race**
- **D:** new pending class hash overwrites existing schedule without explicit cancellation workflow.
- **FP:** overwrite blocked unless prior schedule is canceled/expired.

**85. Library-call governance bypass**
- **D:** privileged flow uses `library_call` path controlled by mutable class hash/registry.
- **FP:** class hash is immutable or strictly timelocked and allowlisted.

**86. Upgrade without initializer version guard**
- **D:** post-upgrade initializer can be replayed because version/init slot is not advanced.
- **FP:** initializer versioning is monotonic and replay-safe.

**87. Caller source confusion in auth check**
- **D:** auth compares wrong caller source (`get_tx_info` account vs `get_caller_address`) for path semantics.
- **FP:** caller primitive matches the actual trust boundary of the entrypoint.

**88. Role grant seeds invalid principal**
- **D:** grant/seed path accepts zero or malformed principal for critical role.
- **FP:** role grant validates non-zero and expected principal domain.

**89. Pause gate bypass via alias entrypoint**
- **D:** one selector is paused while an equivalent alias/casing path remains active.
- **FP:** shared pause gate covers all equivalent privileged/state-mutating routes.

**90. Internal privileged helper exposed externally**
- **D:** helper intended for privileged internal use is reachable through unguarded external wrapper.
- **FP:** every external wrapper enforces the same auth invariant as the helper contract.

**121. Constructor assumes deployer role without explicit grant**
- **D:** constructor logic assumes deployer already holds a specific role (governor, admin) without granting it, causing post-deploy auth failures or requiring external setup.
- **FP:** role is explicitly granted in constructor or documented as a pre-deployment requirement with verified setup script.

**122. Ownership transfer to non-deployed or zero-class contract**
- **D:** `transfer_ownership` or admin transfer accepts any `ContractAddress` without validating the target is a deployed contract or non-zero.
- **FP:** transfer validates target is non-zero and optionally confirms deployment via class hash check.

**123. Forced shutdown override precedence inversion**
- **D:** contract has inferred mode plus forced/shutdown override, but inferred-mode branch returns early before forced override check, leaving critical paths active when forced shutdown is set.
- **FP:** forced override is checked first (or centralized) before any inferred-mode branch logic.

**124. Extension or module parity failure on new capability**
- **D:** new state override or mode (e.g., overwrite shutdown) implemented in core but not propagated to extension/oracle/adapter modules that also enforce the same state.
- **FP:** all extensions implement or delegate to the same capability surface as core.

**125. Non-atomic upgrade-then-configure race window**
- **D:** upgrade (`replace_class_syscall`) and post-upgrade configuration happen in separate transactions, creating a window where new code runs with stale config.
- **FP:** upgrade and migration are atomic (same transaction) or new class is backward-compatible by design.

**126. Multi-sig threshold set below safe minimum**
- **D:** multi-sig or quorum threshold can be set to zero or one, allowing single-signer execution of privileged actions.
- **FP:** threshold setter enforces minimum floor (e.g., `threshold >= 2`) or is immutable.

**127. Factory-deployed contract inherits deployer privilege**
- **D:** factory deploys child contracts that inherit factory address as admin/owner, but factory itself has broad access or is upgradeable.
- **FP:** child contracts use explicit admin parameter independent of factory, or factory is immutable with constrained interface.

**128. Upgrade migration resets or drops permission state**
- **D:** post-upgrade migration overwrites or fails to carry forward role/permission mappings from previous class, silently dropping access control state.
- **FP:** migration explicitly preserves or re-validates all permission state, with post-migration invariant checks.

**129. Self-revocation of sole admin locks governance**
- **D:** sole admin can call `revoke_role` or `renounce_ownership` on themselves with no remaining admin, permanently locking privileged functions.
- **FP:** revocation checks that at least one admin remains, or renounce requires pending transfer to be accepted first.

**130. Arbitrary declared class hash accepted without compatibility verification**
- **D:** upgrade or registry path accepts any declared class hash without validating expected interface, storage-layout compatibility, or migration invariants, allowing incompatible implementations to be installed.
- **FP:** class hash is allowlisted or validated for interface/version/storage compatibility before upgrade, with migration invariants checked explicitly.

**131. Proxy storage layout collision after class replacement**
- **D:** new implementation class uses storage slots that collide with proxy-level state (admin slot, implementation slot), corrupting proxy metadata.
- **FP:** implementation uses explicit non-overlapping Starknet storage namespaces/substorage roots and preserves proxy-reserved metadata invariants.

**132. Authorization/policy check occurs after state mutation on non-reverting error path**
- **D:** function writes critical state before auth/policy check or before a non-reverting `Err` return branch, allowing inconsistent state when the operation reports failure without transaction revert.
- **FP:** auth/policy checks execute before any state write and failure paths do not persist partial mutations.

## references/attack-vectors/attack-vectors-2.md

# Cairo Attack Vectors (2/4): External Calls + Reentrancy + Messaging

**21. CEI violation on ERC1155 transfer path**
- **D:** state mutation happens after `safe_transfer_from` (directly or through helper chain).
- **FP:** critical state committed before interaction or robust non-reentrant guard blocks re-entry.

**22. Session self-call hazard**
- **D:** session execution loop permits calls to account contract itself.
- **FP:** explicit self-target block plus policy constraints prevent self-call.

**23. Selector fallback assumption**
- **D:** on syscall error, logic retries alternate selector and masks root failure.
- **FP:** single canonical selector with fail-fast behavior.

**24. Untrusted callback caller**
- **D:** callback handler mutates state without validating caller contract identity.
- **FP:** callback explicitly pinned to expected contract address.

**25. External call proxy without target constraints**
- **D:** privileged function forwards arbitrary `call_contract_syscall` target/selector/calldata.
- **FP:** strict allowlist, Merkle policy verification, or immutable target set.

**26. Cross-function reentrancy window**
- **D:** helper performs interaction; parent updates critical state after helper returns.
- **FP:** shared lock or state update-before-interaction across entire call chain.

**27. Transfer consistency mismatch**
- **D:** one transfer primitive disabled while equivalent primitive remains enabled (ForgeYields redeem NFT pattern).
- **FP:** mismatch is intentional and protected by additional invariants.

**28. Caller-controlled reward contract invocation**
- **D:** harvest/claim path takes arbitrary reward contract and invokes it.
- **FP:** caller and reward contract are both tightly policy-constrained.

**29. L1/L2 message replay gap**
- **D:** consumed message nonce/hash not tracked, allowing repeated processing.
- **FP:** replay set keyed by unique message hash and nonce.

**30. Callback state confusion with stale flags**
- **D:** callback logic depends on flags written only after interaction.
- **FP:** callback preconditions are finalized before external call.

**31. Fee-transfer call result ignored**
- **D:** transfer return value ignored and state progresses as if payment succeeded.
- **FP:** transfer failure reverts before state transition.

**32. External denial path swallowed**
- **D:** external call failures are caught/logged but execution continues into state writes.
- **FP:** denial path reverts or compensates with full rollback.

**33. ERC721 callback reentrancy on order fill**
- **D:** `transfer_from`/`safe_transfer_from` to callback-capable recipient happens before order status commit.
- **FP:** order status or nonce invariant prevents same-order reentry before callback.

**34. Fee-on-transfer token accounting drift**
- **D:** logic assumes nominal transfer amount equals received amount on external token call.
- **FP:** post-transfer balance delta is used for accounting.

**35. Message cancellation timestamp overwrite**
- **D:** repeated cancellation start overwrites prior timestamp and weakens timeout assumptions.
- **FP:** first cancellation timestamp is immutable or monotonic.

**36. Cross-domain origin verification missing**
- **D:** L1/L2 handler validates payload but not expected sender/domain binding.
- **FP:** explicit sender/domain hash verification in message path.

**37. Library-call target controlled by user input**
- **D:** `library_call`/dispatcher target derives from user-controlled path without allowlist.
- **FP:** target selector pair pinned to immutable registry.

**38. External decoder response schema trust**
- **D:** external decoder/sanitizer output is accepted without shape/value validation.
- **FP:** strict schema and bounds checks on returned payload.

**39. Cross-contract call succeeds with wrong selector casing fallback**
- **D:** retrying camelCase/snake_case on failure accidentally invokes unintended function.
- **FP:** explicit ABI mapping known at compile-time with no runtime selector fallback.

**40. Event emission before interaction finality**
- **D:** event logs success before external call outcome is known, confusing off-chain automation.
- **FP:** success events emitted only after all critical interactions and writes succeed.

**91. L1 handler replay by weak message keying**
- **D:** L1 handler marks processed messages with incomplete key material (missing nonce/sender/value domain).
- **FP:** replay key includes full domain-separated message identity.

**92. Dispatcher callback reentrancy via trusted adapter**
- **D:** adapter marked trusted can still re-enter caller path before state finalization.
- **FP:** reentrancy lock/effects-first invariant spans adapter callback boundaries.

**93. External return decoding without length validation**
- **D:** call result is decoded into struct/tuple without asserting expected span length.
- **FP:** decode path validates result shape/length before interpretation.

**94. Fallback-to-default on external read failure**
- **D:** failed external read (price/balance/config) silently falls back to unsafe default and continues.
- **FP:** failed read reverts or routes to explicit fail-safe mode.

**95. Multicall order dependence on mutable shared state**
- **D:** same transaction can reorder calls to bypass per-call assumptions (auth/limits/nonce checks).
- **FP:** order-independent invariants or explicit topological restrictions are enforced.

**96. Session signature replay across bundled calls**
- **D:** one signature/nonce authorizes multiple operations without per-call binding.
- **FP:** signature domain binds call set/hash and nonce monotonicity per execution.

**97. Bridge emit-before-lock/burn inconsistency**
- **D:** outbound bridge message/event emitted before asset lock/burn is irreversible.
- **FP:** bridge side effects are emitted only after asset state commit.

**98. External call target mutable in same tx path**
- **D:** target address can be changed then used immediately in same execution path without delay.
- **FP:** target mutation and target use are separated by governance delay/checkpoint.

**99. Post-interaction authorization check**
- **D:** external interaction occurs before caller/role authorization is fully validated.
- **FP:** full auth and policy checks happen strictly before any external interaction.

**100. Error-logging without rollback in privileged path**
- **D:** privileged flow catches/logs external error but keeps partial state updates.
- **FP:** privileged path either reverts or atomically compensates all partial state.

**133. Safe-dispatcher panic data drives privileged fallback logic**
- **D:** call through a safe dispatcher returns `Result::Err(panic_data)` and caller parses attacker-controlled `panic_data` to choose a privileged fallback path, effectively turning error payload into authorization/config input.
- **FP:** `Err` branches treat panic payload as opaque diagnostics only; privileged decisions never depend on panic-data content.

**134. Safe-dispatcher fallback assumes catchability of non-catchable syscall failures**
- **D:** fallback logic assumes all external call failures are catchable, but cases like non-existent contract/class hash or Cairo 0 edge paths revert the entire transaction and bypass fallback.
- **FP:** fallback logic is limited to documented catchable failure modes and pre-validates contract/class existence where needed.

**135. Deserialization failure in try_* syscall wrapper causes unexpected revert**
- **D:** `try_call_contract` or similar wrapper reverts on deserialization failure of the return value instead of returning an error, breaking fallback logic.
- **FP:** wrapper handles both call failure and decode failure paths, or return type is guaranteed by target ABI.

**136. L1/L2 message ordering gap enables blocking attack**
- **D:** `update_state` or L1 handler processes messages sequentially; a single malformed or oversized message blocks all subsequent messages in the batch.
- **FP:** messages are processed independently with per-message error isolation, or batch validation rejects invalid entries before processing.

**137. Race condition in multi-step token activation**
- **D:** token bridge activation requires multiple transactions (deploy + register + configure); between steps, another actor can front-run or interfere with an incomplete activation.
- **FP:** activation is atomic or protected by a pending-state lock that blocks interference.

**138. Optional token metadata/interface assumption without capability check**
- **D:** protocol assumes optional token metadata/interface functions (for example `decimals()`) are present and trusted, causing runtime failure or incorrect normalization on non-standard tokens.
- **FP:** token capability is validated at registration and missing optional interfaces are handled explicitly.

**139. Cross-chain bridge missing rate limit or circuit breaker**
- **D:** single bridge transaction can drain the entire locked pool with no per-transaction cap or pause mechanism.
- **FP:** bridge enforces per-tx and per-period caps with automatic pause on anomalous volume.

**140. Batch executor reuses stale external-state cache across legs**
- **D:** batched execution caches external state (allowance/balance/config) from an early leg and reuses it after intervening calls mutate that state, so even fixed call ordering can execute against invalid assumptions.
- **FP:** each leg refreshes external state at point-of-use or invalidates cache on every state-changing leg.

**141. Excessive message or output size causes DoS in state update**
- **D:** bridge `update_state` or message handler does not validate input array sizes; excessively large payloads cause out-of-gas or computation overflow.
- **FP:** handler enforces maximum array/message size bounds before processing.

**142. Registry-to-dispatch TOCTOU on class-hash validation**
- **D:** class hash is validated only at registration time, but dispatch uses a mutable registry value later without re-validating against current allowlist/snapshot.
- **FP:** dispatch re-validates class hash against immutable snapshot/allowlist at execution time.

**143. Callback during token transfer enables cross-protocol reentrancy**
- **D:** ERC721/ERC1155 safe transfer triggers receiver callback; receiving contract re-enters a different protocol function that reads stale state from the transferring contract.
- **FP:** cross-protocol reentrancy guard or effects-before-interaction pattern across all dependent state.

**144. Bridge message hash collision via non-canonical preimage encoding**
- **D:** bridge participants hash semantically identical messages with different field packing/serialization rules (felt vs bytes layout, padding, or array framing), creating hash mismatches or collisions across domains.
- **FP:** bridge defines one canonical preimage encoding shared by all participants and enforces it with cross-implementation test vectors.

## references/attack-vectors/attack-vectors-3.md

# Cairo Attack Vectors (3/4): Math + Pricing + Economic Logic

**41. Unchecked fee bound**
- **D:** external/config fee parameter stored or forwarded without explicit max bound.
- **FP:** same-path assertion enforces max fee.

**42. Fee recipient zero-address DoS**
- **D:** fee recipient accepted as zero and later transfer path reverts permanently (ForgeYields-style risk).
- **FP:** recipient validated non-zero or transfer path handles zero safely.

**43. Rounding bias against one side**
- **D:** repeated rounding direction systematically favors protocol/one actor over time.
- **FP:** bias is documented, bounded, and validated by invariants.

**44. Felt/int boundary misuse**
- **D:** implicit felt<->u* conversion in accounting without range checks.
- **FP:** checked conversion and bounded domain assertions.

**45. Underflow guarded only by assumptions**
- **D:** subtraction in accounting path lacks explicit `amount <= balance` check.
- **FP:** invariant or guard enforces safe subtraction precondition.

**46. Oracle trust concentration**
- **D:** single role controls critical pricing/AUM with weak or absent second-layer limits.
- **FP:** independent caps, delay, quorum, or circuit-breakers constrain updates.

**47. Fee-share dilution drift**
- **D:** repeated fee-mint formula introduces systematic dilution beyond intended policy.
- **FP:** fee accrual invariants bound drift and match policy math.

**48. Boundary double-spend across period reset**
- **D:** hard-window reset permits max spend just before and after boundary.
- **FP:** sliding window or explicit boundary hardening.

**49. Zero-amount side-effect call**
- **D:** zero-value `approve/transfer` or policy call still changes privileges or approvals.
- **FP:** zero-value side-effecting selectors are blocked.

**50. Denominator stabilization hack leakage**
- **D:** ad-hoc `+1` denominator/sentinel math leaks value over repeated operations.
- **FP:** mathematically justified stabilization with bounded error tests.

**51. Multi-token spending-policy mismatch**
- **D:** policy accounting binds to one token while selectors can move others.
- **FP:** selector gate enforces same token domain as spending policy.

**52. Repricing path bypasses caps**
- **D:** cap checked on one update path but bypassed through alternate report/settlement path.
- **FP:** shared invariant enforced for every repricing entrypoint.

**53. Division-before-multiplication precision loss**
- **D:** integer division happens before scaling multiply in value-sensitive calculations.
- **FP:** multiply-then-divide with overflow-safe helper is used.

**54. WAD/RAY scale mismatch across modules**
- **D:** mixed precision units combined without explicit conversion.
- **FP:** explicit scale conversion and tests across module boundaries.

**55. Signed/unsigned tick or price cast hazards**
- **D:** unchecked cast between signed tick and unsigned storage value.
- **FP:** bounds checks and safe cast wrappers enforce domain.

**56. Slippage guard missing on liquidity ops**
- **D:** add/remove liquidity path lacks min-out/max-in assertions.
- **FP:** slippage constraints validated on all liquidity-changing routes.

**57. Comparator misuse in bounds validation**
- **D:** wrong comparator helper (`is_le`-style misuse seen in Cartridge audit class) accepts invalid range.
- **FP:** comparator semantics explicitly tested on edge values.

**58. Price bound mismatch with decimal scaling**
- **D:** price limit compared in different decimal domains.
- **FP:** both operands normalized to the same precision before comparison.

**59. Reward distribution overcounts due stale accumulator**
- **D:** distribution uses stale accumulator snapshot after state changed.
- **FP:** accumulator is refreshed before every distribution computation.

**60. Epoch settlement can be blocked by negative/overflow edge**
- **D:** settlement path panics on signed-edge value and blocks epoch close.
- **FP:** settlement branch handles signed edge and remains progress-safe.

**101. Liquidation bonus rounding inversion**
- **D:** liquidation reward math rounds in favor of liquidator when policy requires protocol/user protection.
- **FP:** rounding direction is explicit, documented, and invariant-tested.

**102. Cap check after value mint/accounting mutation**
- **D:** mint/accounting state is updated before cap/limit assertion executes.
- **FP:** cap/limit validation is enforced prior to state mutation.

**103. Interest accrual uses stale timestamp snapshot**
- **D:** accrual math reuses stale/cached timestamp state (or validation-phase rounded timestamp) instead of refreshing execution-phase time at accounting boundary.
- **FP:** accrual reads execution-phase timestamp fresh at settlement/accounting boundary and tests validation-vs-execution semantics.

**104. BPS denominator mismatch**
- **D:** one path uses `10_000` while another uses alternate denominator for same fee/rate domain.
- **FP:** denominator constant is shared and tested across all rate paths.

**105. Signed funding-rate clamp asymmetry**
- **D:** positive and negative funding bounds are clamped differently, creating directional leakage.
- **FP:** symmetric clamp policy with explicit signed-bound tests.

**106. Batch update bypasses cumulative delta guard**
- **D:** per-item delta checks pass while aggregate batch delta exceeds policy limit.
- **FP:** both per-item and cumulative deltas are enforced.

**107. Decimal normalization source mismatch**
- **D:** normalization uses one asset's decimals for another asset path.
- **FP:** each asset path resolves and validates its own decimal domain.

**108. Multiply chain overflow before safe divide**
- **D:** high-order multiply happens before overflow-safe divide/cast boundary.
- **FP:** operation ordering or helpers ensure overflow-safe intermediate math.

**109. Timestamp/block-number domain confusion**
- **D:** staleness/expiry check compares timestamp-based values to block-number domain.
- **FP:** freshness checks are domain-consistent and unit-tested.

**110. Reciprocal pricing missing zero/underflow guards**
- **D:** inverse price math executes without zero-floor and bound checks.
- **FP:** reciprocal path validates non-zero numerator/denominator and bounds.

**145. Felt252 range violation via unbounded parameter**
- **D:** parameter passed to cryptographic function, bit operation, or storage derivation is not bounds-checked against safe range below felt252 field prime, enabling wrap-around or invalid proof inputs.
- **FP:** explicit range assertion (e.g., `value < 2^64`, `bit_size < 252`) enforced before use.

**146. Modulo arithmetic edge case causing index wrap or overwrite**
- **D:** index derived via modulo (`value % CAPACITY`) wraps to a previously used slot, overwriting pending state (e.g., root history ring buffer).
- **FP:** modulo-derived index checked for collision with occupied slot, or monotonic index prevents overwrite.

**147. Collected fee balance stuck with no withdrawal mechanism**
- **D:** contract collects fees (transfer-in on operations) but exposes no function to withdraw accumulated fee balance, permanently locking funds.
- **FP:** dedicated fee withdrawal function exists with appropriate access control.

**148. Fee hook or callback always reverts blocking operation**
- **D:** fee collection path delegates to hook/callback that unconditionally reverts (wrong interface, missing implementation, or incorrect return value), blocking all fee-bearing operations.
- **FP:** fee hook is validated at registration or has a bypass/fallback for misconfigured hooks.

**149. First-depositor vault share inflation attack**
- **D:** first depositor mints minimal shares then donates assets directly, inflating share price so subsequent depositors receive zero shares for non-trivial deposits.
- **FP:** vault enforces minimum initial deposit, uses virtual shares/assets offset, or dead shares mechanism.

**150. Instruction sequence interaction causing negative balance state**
- **D:** specific combination of batch instructions (borrow + swap + repay) creates intermediate negative balance that either reverts unexpectedly or underflows storage.
- **FP:** batch executor validates intermediate invariants between instructions, or instruction ordering is constrained.

**151. Staking reward front-run by new depositor before checkpoint**
- **D:** reward distribution checkpoint occurs after new stake is recorded, allowing just-in-time depositor to claim share of pending rewards without contributing to the earning period.
- **FP:** checkpoint/accumulator updated before new stake is recorded, or time-weighted distribution prevents instantaneous dilution.

**152. Cross-market timestamp-key alias contaminates accrual state**
- **D:** two market/reward streams resolve to the same timestamp storage key (aliasing bug), so updates in one stream silently mutate accrual baseline of another.
- **FP:** each stream has a unique storage key namespace and invariant tests prove no cross-market key aliasing.

**153. Route-level slippage check omitted despite per-leg checks**
- **D:** each leg in a batched route satisfies local min/max checks, but no final route-level min-out/slippage assertion is enforced at settlement, allowing harmful composite paths.
- **FP:** protocol enforces both per-leg checks and a final route-level aggregate slippage/invariant check.

**154. Emergency withdrawal fails due to insufficient contract balance**
- **D:** emergency withdrawal path assumes contract holds sufficient assets, but partial withdrawals or external drains leave insufficient balance, causing revert when users need funds most.
- **FP:** emergency path handles partial fulfillment or has priority claim mechanism with clear accounting.

**155. Deficit handling branch reachable but debt accounting not persisted**
- **D:** settlement reaches the deficit/socialization path but fails to persist updated debt/socialization state before exit, causing repeated deficit handling or inconsistent recovery accounting.
- **FP:** deficit branch atomically persists debt/socialization state and integration tests cover consecutive negative-settlement cycles.

**156. Fee override path bypasses canonical normalization**
- **D:** primary setter normalizes fee inputs correctly, but admin/import/override path writes raw fee values directly, letting consumers apply mixed-scale state.
- **FP:** every fee write path goes through the same normalization helper, and override/import routes are tested against the canonical stored unit.

## references/attack-vectors/attack-vectors-4.md

# Cairo Attack Vectors (4/4): Storage + Components + Trust Chains

**61. Constructor dead parameter**
- **D:** constructor accepts security-critical parameter but never uses it (ForgeYields redeem request pattern).
- **FP:** parameter is explicitly deprecated/compat-only and cannot affect privilege/invariants.

**62. Map-zero default confusion**
- **D:** zero default map entry is treated as initialized/authorized state.
- **FP:** explicit existence flag or non-zero sentinel separates unset from set.

**63. Stale storage after burn/revoke**
- **D:** post-burn/revoke mappings remain active where logic assumes deletion.
- **FP:** retention is intentional and read paths are gated accordingly.

**64. Cross-contract role dependency break**
- **D:** auth depends on mutable external contract state that can desync or be upgraded unexpectedly.
- **FP:** dependency immutable or validated with integrity checks.

**65. External decoder/sanitizer trust assumption**
- **D:** proof/policy validation relies on external decoder contract without integrity pinning.
- **FP:** decoder identity pinned and governed with fail-safe fallback.

**66. Immutable dependency without recovery path**
- **D:** critical dispatcher/dependency set once and not recoverable after failure.
- **FP:** immutable-by-design and explicitly covered by ops runbook/threat model.

**67. Registry hash/domain mismatch**
- **D:** hashed keys/signatures omit domain separator or key-length semantics.
- **FP:** domain-separated and length-aware hashing.

**68. Nonce monotonicity gap across transitions**
- **D:** nonce not incremented on unset/reset transitions, enabling stale signature replay.
- **FP:** nonce increments on every transition that invalidates signed intent.

**69. Event visibility gap for bulk revocation**
- **D:** bulk state updates omit per-item events needed by indexers.
- **FP:** per-item event emission or equivalent query-safe indexing contract.

**70. Upgrade audit trail loss**
- **D:** upgrade/change event omits prior class hash/identifier.
- **FP:** old and new identifiers emitted atomically.

**71. Initialization branch deadlock**
- **D:** constructor config can permanently disable required init branch.
- **FP:** constructor validates branch preconditions or exposes safe alternate init.

**72. Over-broad registry persistence**
- **D:** helper registry accepts arbitrary writes enabling spam/indexer DoS.
- **FP:** bounded writes, scoped permissions, or explicit pagination caps.

**73. Nonce domain collision across action types**
- **D:** same nonce key reused for distinct actions (set/unset/upgrade) enabling cross-action replay.
- **FP:** nonce scope includes action domain and contract context.

**74. Storage key composition collision**
- **D:** composite storage keys omit one discriminator and collide across logical records.
- **FP:** full key tuple encoded in storage address/hash derivation.

**75. Merkle/root history overwrite without uniqueness check**
- **D:** root history accepts repeated/invalid replacement that weakens finality assumptions.
- **FP:** root insertion enforces expected progression and duplicate handling rules.

**76. Proof verifier address mutability without governance delay**
- **D:** verifier endpoint mutable via immediate admin call, enabling sudden trust shift.
- **FP:** verifier changes timelocked and event-auditable.

**77. ABI variant fallback masks integration breakage**
- **D:** integration tries multiple ABI variants and proceeds on partial decode assumptions.
- **FP:** one canonical ABI and strict decode failure handling.

**78. Pending-owner/admin stale state leak**
- **D:** ownership transfer leaves stale pending/old authority state that still influences checks.
- **FP:** old pending authority cleared on transfer completion.

**79. Wrong event payload branch**
- **D:** event emits mismatched payload (for example claim payload in refund path), causing off-chain accounting to overcount due to stale accumulator state.
- **FP:** event payload strictly tied to executed branch and tested.

**80. Unbounded user-controlled iteration**
- **D:** loops over user-controlled array/span without bound checks can DoS execution.
- **FP:** explicit max bounds and fail-fast checks enforce bounded work.

**111. Component storage namespace overlap**
- **D:** Cairo component composition assigns overlapping component storage base/namespace, so writes in one component corrupt another.
- **FP:** component storage namespaces are uniquely derived at composition time and collision-tested across all embedded components.

**112. Derived storage key missing discriminator**
- **D:** application-level derived key generation (for example via `storage_address_from_base`) omits record/action discriminator and aliases unrelated records.
- **FP:** derived key includes full logical tuple (domain + action + actor + nonce/id) and is invariant-tested for alias resistance.

**113. Class-hash registry downgrade without monotonicity**
- **D:** registry accepts class hash replacement without version/epoch monotonicity checks.
- **FP:** upgrades enforce monotonic versioning and downgrade policy.

**114. Revocation leaves active authorization residue**
- **D:** revocation clears primary role map but leaves secondary authorization surface (cache/root/session capability) still accepted by auth checks.
- **FP:** revoke flow invalidates every authorization surface (role map + capability cache/root/session state) consumed by runtime checks.

**115. Queue/index wraparound overwrite**
- **D:** bounded index/counter wraps and overwrites pending operation state.
- **FP:** queue/index arithmetic enforces monotonic non-overwrite behavior.

**116. Hash preimage ambiguity in composite keys**
- **D:** composite key hashing mixes heterogeneous field encodings (felt packing/spans/byte arrays) without canonical boundaries, creating preimage aliasing.
- **FP:** composite keys use canonical encoding with explicit domain/version separators and deterministic field boundaries.

**117. Signature domain omits chain or contract binding**
- **D:** signature domain binds action/nonce but omits Starknet chain-id or verifier/account contract binding, enabling cross-deployment replay.
- **FP:** signature domain explicitly binds chain-id, verifying contract/account context, action, and nonce.

**118. Upgrade migration not idempotent**
- **D:** migration step can be re-run and mutates state inconsistently on repeat execution.
- **FP:** migration guarded by version bit and repeat-safe behavior.

**119. Trusted relayer set mutation lacks auditability**
- **D:** relayer/trusted-actor mutation occurs without event trail or immutable checkpoint.
- **FP:** mutation emits complete audit event and is recoverable/observable.

**120. Cross-module invariant gap after dependency swap**
- **D:** dependency swap updates pointer successfully but does not revalidate post-swap invariants across tightly-coupled modules, leaving inconsistent cross-module state.
- **FP:** swap path performs explicit post-swap invariant revalidation across coupled modules and aborts on inconsistency.

**157. Storage key collision via duplicate token or pool identifiers**
- **D:** adding a pool/token with the same identifier (address, denomination) overwrites the existing mapping entry silently, corrupting prior state.
- **FP:** registration path checks for existing entry and reverts or uses unique composite key.

**158. Dynamic felt252 array storage with incorrect size tracking**
- **D:** `StoreFelt252Array` or manual array storage writes elements but stores incorrect length, causing reads to return truncated or out-of-bounds data.
- **FP:** array length updated atomically with element writes and validated on read.

**159. ByteArray/felt encoding accepts malformed input without validation**
- **D:** `ByteArray` or raw felt spans are accepted from external input without validating canonical encoding (non-canonical `bytes31` padding, declared-length mismatch, oversized chunk values), causing downstream decode failures.
- **FP:** input validation at the boundary rejects malformed ByteArray/felt encoding before storage or processing.

**160. Array element removal leaves stale reference or index gap**
- **D:** removing element from storage array (swap-and-pop or shift) does not update all secondary indices/references, leaving stale pointers (e.g., lock ID array after unlock).
- **FP:** removal atomically updates all dependent indices and validates consistency.

**161. State counter not updated in secondary function path**
- **D:** primary function updates a state counter (e.g., `notesCount`, `totalDeposits`) but secondary function that also modifies the underlying data skips the counter update.
- **FP:** all mutation paths update the shared counter, enforced by shared internal helper.

**162. Fiat-Shamir challenge omits public input binding in proof composition**
- **D:** challenge derivation in zero-knowledge proof composition omits critical public inputs (bit proofs, commitments, statement components), allowing proof forgery via input substitution.
- **FP:** challenge hash includes all public inputs, statement components, and proof-stream commitments per protocol specification.

**163. Signature replay via omitted nonce or commitment binding**
- **D:** signed payload (encrypted notes, transfer authorization) does not bind to a unique nonce or commitment, allowing the same signature to authorize multiple distinct operations.
- **FP:** signature domain includes monotonic nonce, unique commitment hash, and operation-specific context.

**164. Multisig or aggregation ISM allows duplicate signatures**
- **D:** multi-signature verification (ISM, multisig wallet) counts duplicate signatures from the same signer toward the threshold, allowing single signer to reach quorum.
- **FP:** verification deduplicates signers before counting, or enforces strictly ascending signer order.

**165. Repeated state update overwrites pending timestamp or deadline**
- **D:** calling the same state-update function again (e.g., `start_cancellation`, `start_withdrawal`) overwrites the original timestamp, resetting or extending the waiting period.
- **FP:** repeated call blocked while pending, or first timestamp is immutable until completion/expiry.

**166. Non-reverting failure path lacks durable failure signal**
- **D:** operation records a non-reverting failure state (for example status flag/partial retry state) but emits no event or durable queryable marker, leaving off-chain systems blind.
- **FP:** non-reverting terminal outcomes emit explicit failure events or write a queryable failure-state record.

**167. Cross-function state inconsistency via unsynchronized counters**
- **D:** two functions read/write the same logical counter (e.g., `notesCount`) but one uses stale or differently-scoped state, producing inconsistent results across queries.
- **FP:** shared counter accessed through single internal getter/setter with consistent scope.

**168. Proof or commitment reuse across distinct protocol actions**
- **D:** zero-knowledge proof, encrypted note, or commitment valid for one action (deposit) can be replayed for a different action (withdrawal) because the action type is not bound in the proof domain.
- **FP:** proof/commitment domain explicitly includes action discriminator preventing cross-action replay.

**169. ContractAddress/felt252 narrowing mismatch for 32-byte external identifiers**
- **D:** cross-chain or interop path receives a 32-byte identifier and narrows it into `ContractAddress` (`[0, 2**251)`) or `felt252` (`< P`, where `P = 2**251 + 17*2**192 + 1`) via unchecked truncation/modular reduction, causing collisions or invalid mappings.
- **FP:** external identifiers remain in explicit 256-bit storage (`u256`/byte array) and boundary conversions into `ContractAddress`/`felt252` are explicit, range-checked, and fail-fast.

**170. Aggregation element-count truncation via unsafe narrow/wrapping conversion**
- **D:** aggregation module narrows validator/message count into small integer (`u8`) via wrapping/unchecked conversion; counts above boundary are truncated and quorum checks mis-evaluate.
- **FP:** count stays in sufficiently large type (`u32`/`u64`) or narrowing is guarded by explicit upper-bound assertion with fail-fast revert.

## references/audit-findings

```

```

## references/audit-findings/README.md

# Audit Findings Corpus

This directory stores imported/legacy notes.

Canonical normalized findings now live under:

- `../../../datasets/normalized/findings/`

Canonical distilled cards now live under:

- `../../../datasets/distilled/vuln-cards/`

Do not store confidential client identifiers in either location.

## references/audit-findings/source-cairo-security-import.md

---
name: cairo-security
description: Use when reviewing Cairo contracts for security — common vulnerabilities, audit patterns, production hardening, Cairo-specific pitfalls, L1/L2 bridging safety, session key security, precision/rounding bugs, static analysis tooling. Sourced from 50+ public audits and the Cairo Book.
license: Apache-2.0
metadata: {"author":"omarespejel","version":"3.2.0","last_updated":"2026-02-11","org":"keep-starknet-strange","github":"https://github.com/omarespejel","x":"https://x.com/omarespejel"}
keywords: [cairo, security, audit, vulnerabilities, access-control, reentrancy, starknet, production, hardening, l1-l2, session-keys, precision, rounding, static-analysis, snip-12, snip-9, outside-execution, governance, pausable, paymaster, account-abstraction, storage-node, vec, map, felt252, erc4626, erc20-permit]
allowed-tools: [Bash, Read, Write, Glob, Grep, Task]
user-invocable: true
---

# Cairo Security

Security patterns and common vulnerabilities for Cairo smart contracts on Starknet. Sourced from 50+ public audit reports including Nethermind, ConsenSys Diligence, Code4rena, ChainSecurity, Cairo Security Clan, Zellic, and Nethermind AuditAgent, plus the [Cairo Book security chapter](https://book.cairo-lang.org/ch104-01-general-recommendations.html), [Crytic's Not So Smart Contracts](https://github.com/crytic/building-secure-contracts/tree/master/not-so-smart-contracts/cairo), [Oxor.io Cairo Security Flaws](https://oxor.io/blog/2024-08-16-cairo-security-flaws/), and [FuzzingLabs Top 4 Vulnerabilities](https://fuzzinglabs.com/top-4-vulnerability-cairo-starknet-smart-contract/).

> **Versions (validated as of 2026-03-08):** Cairo **v2.15.0** ([release tag](https://github.com/starkware-libs/cairo/releases/tag/v2.15.0)), Scarb **2.15.1**, Starknet Foundry **0.56.0** ([release tag](https://github.com/foundry-rs/starknet-foundry/releases/tag/v0.56.0)), OpenZeppelin Contracts for Cairo **v3.0.0** ([release tag](https://github.com/OpenZeppelin/cairo-contracts/releases/tag/v3.0.0)), and Starknet **v0.14.1**. Canonical docs: [Cairo Book](https://www.starknet.io/cairo-book/), [Corelib docs](https://docs.starknet.io/build/corelib/intro), [Starknet Foundry book](https://foundry-rs.github.io/starknet-foundry/), [OpenZeppelin Cairo 3.x docs](https://docs.openzeppelin.com/contracts-cairo/3.x).

> **Cairo Editions:** Cairo v2.15.0 introduced `edition 2025_12`, which changes snapshot/member access syntax (e.g., `(@a).b` returns desnapped value). If your `Scarb.toml` specifies this edition, test code that accesses struct members through snapshots — the number of `@` levels needed may differ from pre-2025_12 behavior.

> **Workflow:** Use this skill as a review pass after your contract compiles and tests pass. Not a replacement for a professional audit.

## When to Use

- Reviewing a contract before audit or deployment
- Checking for common Cairo/Starknet vulnerabilities
- Hardening a contract for production
- Implementing access control, upgrade safety, input validation
- Writing session key or delegated execution contracts
- Reviewing L1/L2 bridge handlers

**Not for:** Writing contracts (use cairo-contract-authoring), testing (use cairo-testing), gas optimization (use cairo-optimization)

## Critical Patterns — Read These First

These are the highest-impact Cairo/Starknet security patterns. Each has caused real losses or was flagged in multiple audits. If you read nothing else, read these.

1. **`felt252` division is modular inverse, not floor division.** `felt252_div(10, 3)` does NOT return 3. It returns a huge field element. Never use `felt252` for financial math — use `u256` or `u128`. (Section 7)

2. **`Map.read()` returns zero on missing keys — no panic.** An attacker bypassed oracle validation by reading a non-existent key that returned zero, then signed over zeroed data. Always assert non-zero/non-default after reading from storage Maps. (Section 4, Section 16 C4 Perpetual H-01)

3. **`felt252` arithmetic wraps silently.** `balance - amount` where `amount > balance` wraps to a huge number with no error. Use `u256`/`u128` for all balances, amounts, prices. (Section 7)

4. **Floor division always favors the actor.** When burning/withdrawing, round UP against the user. When minting/depositing, round DOWN against the user. The zkLend $10M exploit chained precision loss with accumulator manipulation. (Section 3)

5. **Empty market initialization + flash loan = catastrophic.** First depositor controls the exchange rate. Lock minimum liquidity on first deposit. Applies to lending pools and ERC-4626 vaults. (Section 3)

6. **OZ embedded impls leak privileged selectors to session keys.** Every OZ version exposes new selectors (`set_public_key`, `setPublicKey`, `upgrade`). Block self-calls from session keys: `assert(call.to != get_contract_address())`. (Section 13)

7. **SNIP-9 `execute_from_outside` needs nonce + caller + time bounds.** Missing any one enables replay attacks. Signature must be validated via SNIP-12 over the full `OutsideExecution` struct. (Section 14)

8. **Starknet v0.14.0 killed v0/v1/v2 transactions and cut blocks to ~6s.** Time-dependent logic calibrated for 30s blocks is now wrong by 5x. STRK-only fees. (Section 15)

9. **`__validate__` in custom accounts must be lightweight.** No storage writes (except nonce), no external calls, bounded gas. Expensive validation griefs the sequencer. (Section 12)

10. **Checks-effects-interactions is not optional.** C4 Starknet Perpetual H-02: state diff applied before validation caused double-application. C4 Opus H-01: `charge()` called after computing withdrawal amount overwrote the result. (Section 2, Section 16)

---

## Pre-Deployment Checklist

Before any mainnet deployment:

- [ ] All tests pass (`snforge test`) including fuzz tests for arithmetic-heavy logic
- [ ] Fuzz tests written for arithmetic-heavy and state-transition logic (`snforge test --fuzzer-runs 500`)
- [ ] No `unwrap()` on user-controlled inputs — use `expect()` or pattern match
- [ ] Access control on all state-changing functions
- [ ] Zero-address checks on constructor arguments
- [ ] Initializer can only be called once (use OZ `InitializableComponent`)
- [ ] Events emitted for all state changes (upgrades, config, pausing, privileged actions)
- [ ] No storage collisions between components
- [ ] Upgrade function protected by owner/admin check
- [ ] Checks-effects-interactions pattern on all external calls
- [ ] ReentrancyGuard on functions that make external calls before state updates
- [ ] No unbounded loops on user-controlled data
- [ ] L1 handler validates `from_address` against trusted L1 contract
- [ ] Boolean returns from ERC20 `transfer`/`transfer_from` checked
- [ ] Operator precedence verified in complex boolean expressions
- [ ] Bit-packing does not exceed 251 bits for felt252
- [ ] Precision/rounding in division reviewed — truncation can be exploited (see Section 3)
- [ ] Nonces used for all signature-gated operations (see Section 5)
- [ ] No sensitive data stored in plaintext on-chain (see Section 6)
- [ ] Market initialization protected against empty-state manipulation
- [ ] Contract verified on block explorer
- [ ] `LegacyMap` migrated to `Map` (Cairo 2.7+)
- [ ] Storage `Map.read()` results validated when absence should be an error (returns zero, not panic)
- [ ] `felt252` not used for balances, amounts, prices, or counters (use `u256`/`u128`)
- [ ] SNIP-12 used for all off-chain signature verification (not raw Pedersen hashing)
- [ ] SNIP-9 `execute_from_outside` validates nonce, caller, and time bounds
- [ ] `__validate__` in custom accounts is lightweight and makes no external calls
- [ ] Liquidation/risk-management functions NOT blocked by pause mechanism
- [ ] Paymaster interactions rate-limited and allowlisted
- [ ] `NoncesComponent` from OZ used for replay protection (not hand-rolled nonces)
- [ ] V3 transaction resource bounds handled (STRK-only fees since v0.14.0)
- [ ] ERC-4626 vault first-depositor protection applied (minimum liquidity lock or virtual shares/assets) if applicable
- [ ] `PausableComponent` integrated with exclusions for liquidation/risk functions
- [ ] Public key inputs validated to lie on the STARK curve
- [ ] No legacy v0/v1/v2 transaction assumptions (deprecated since v0.14.0)
- [ ] Time-dependent logic recalibrated for ~6s block time (v0.14.0)
- [ ] Global validation functions scoped correctly — no cross-contamination where unrelated state failures block valid operations
- [ ] Per-asset risk parameters (not one-size-fits-all) for price staleness, funding caps, collateral factors
- [ ] `AccessControlDefaultAdminRulesComponent` used for admin role transfer delay

---

## 1. Access Control, Upgrades & Initializers

*Source: [Cairo Book ch104](https://book.cairo-lang.org/ch104-01-general-recommendations.html), [Code4rena Starknet Perpetual H-02](https://code4rena.com/reports/2025-03-starknet-perpetual)*

The most common critical findings in Starknet audits are "who can call this?" and "can this be re-initialized?"

### Missing Access Control

```cairo
// BAD — anyone can mint
fn mint(ref self: ContractState, to: ContractAddress, amount: u256) {
    self.erc20.mint(to, amount);
}

// GOOD — only minter role
fn mint(ref self: ContractState, to: ContractAddress, amount: u256) {
    self.access_control.assert_only_role(MINTER_ROLE);
    self.erc20.mint(to, amount);
}
```

### Unprotected Upgrade (Full Contract Takeover)

If a non-authorized user can upgrade, they replace the class with anything and get full control.

```cairo
// BAD — anyone can upgrade
fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {
    self.upgradeable.upgrade(new_class_hash);
}

// GOOD — owner-only, with event
fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {
    self.ownable.assert_only_owner();
    self.upgradeable.upgrade(new_class_hash);
    self.emit(Upgraded { new_class_hash });
}
```

### Re-Initializable Initializer

A publicly exposed initializer that can be called post-deploy is a frequent vulnerability.

```cairo
// BAD — can be called multiple times
fn initializer(ref self: ContractState, owner: ContractAddress) {
    self.ownable.initializer(owner);
}

// GOOD — one-shot guard
#[storage]
struct Storage {
    initialized: bool,
}

fn initializer(ref self: ContractState, owner: ContractAddress) {
    assert!(!self.initialized.read(), "ALREADY_INIT");
    self.initialized.write(true);
    self.ownable.initializer(owner);
}
```

**Rule:** If it must be external during deployment, make sure it can only be called once. If it doesn't need to be external, keep it internal.

---

## 2. Checks-Effects-Interactions (Reentrancy)

*Source: [0xEniotna/Starknet-contracts-vulnerabilities](https://github.com/0xEniotna/Starknet-contracts-vulnerabilities), Code4rena Starknet Perpetual H-02*

Code4rena's H-02 finding on Starknet Perpetual: `_execute_transfer` applied state diffs *before* performing checks. Always: check, then update state, then call external contracts.

```cairo
// BAD — state update after external call (reentrancy window)
fn withdraw(ref self: ContractState, amount: u256) {
    let caller = get_caller_address();
    let balance = self.balances.read(caller);
    assert(balance >= amount, 'Insufficient balance');

    IERC20Dispatcher { contract_address: self.token.read() }
        .transfer(caller, amount);       // external call FIRST

    self.balances.write(caller, balance - amount);  // state update AFTER
}

// GOOD — checks-effects-interactions
fn withdraw(ref self: ContractState, amount: u256) {
    let caller = get_caller_address();
    let balance = self.balances.read(caller);
    assert(balance >= amount, 'Insufficient balance');

    self.balances.write(caller, balance - amount);  // state update FIRST

    IERC20Dispatcher { contract_address: self.token.read() }
        .transfer(caller, amount);       // external call LAST
}
```

---

## 3. Precision, Rounding & Accumulator Manipulation

*Source: [BlockSec — zkLend Exploit Post-Mortem (Feb 2025)](https://blocksec.com/blog/zklend-exploit-post-mortem), [FuzzingLabs zkLend Analysis](https://fuzzinglabs.com/rediscovery-zklend-hack/)*

The zkLend exploit ($10M, Feb 12, 2025) is the largest Cairo-specific exploit to date. Root cause: precision loss through truncation in division, combined with accumulator manipulation via flash loan donations in an empty market.

### The Attack Pattern

1. **Empty market initialization** — attacker deposits 1 wei into an empty lending pool. Both `reserve_balance` and `ztoken_supply` start at 0 with `lending_accumulator = 1`.
2. **Accumulator inflation via flash loan donations** — attacker takes flash loans of 1 wei, repays 1000 wei. Excess is treated as a "donation" that inflates `lending_accumulator`. After 10 flash loans: accumulator reaches ~4.069 × 10^18.
3. **Rounding exploitation** — with a huge accumulator, `scaled_down_amount = amount / lending_accumulator` uses floor division (truncation). Burning tokens decreases `raw_balance` by only 1 unit despite burning large token amounts. Repeated deposit/withdraw cycles increment `raw_balance` by 1 each cycle.
4. **Profit extraction** — `raw_balance` reaches 1,724 → collateral value of 7,015 wstETH → borrow other assets from the market.

### Defense Patterns

```cairo
// PATTERN 1: Minimum liquidity lock (prevent empty-market manipulation)
// On first deposit, lock a minimum amount permanently
fn first_deposit(ref self: ContractState, amount: u256) {
    let MIN_LIQUIDITY: u256 = 1000;  // dead shares
    assert(amount > MIN_LIQUIDITY, 'BELOW_MIN_LIQUIDITY');
    // Mint MIN_LIQUIDITY shares to zero address (locked forever)
    self._mint(Zeroable::zero(), MIN_LIQUIDITY);
    // Mint remainder to depositor
    self._mint(get_caller_address(), amount - MIN_LIQUIDITY);
}

// PATTERN 2: Guard accumulator changes per transaction
fn settle_extra_reserve(ref self: ContractState) {
    let new_acc = self._compute_accumulator();
    let old_acc = self.lending_accumulator.read();
    let MAX_ACC_CHANGE: u256 = old_acc / 10; // max 10% change per tx
    assert(new_acc - old_acc <= MAX_ACC_CHANGE, 'ACC_CHANGE_TOO_LARGE');
    self.lending_accumulator.write(new_acc);
}

// PATTERN 3: Round UP when burning shares (penalize withdrawer, not pool)
fn burn_scaled(amount: u256, accumulator: u256) -> u256 {
    // Round up: (amount + accumulator - 1) / accumulator
    (amount + accumulator - 1) / accumulator
}
```

### Key Takeaway

Floor division in share/token math always favors the actor performing the operation. **When burning/withdrawing, round UP (against the user). When minting/depositing, round DOWN (against the user).** This ensures the pool never loses value through rounding.

### Fuzz Testing for Precision Bugs

```cairo
#[test]
#[fuzzer(runs: 1000)]
fn test_deposit_withdraw_invariant(deposit_amount: u256) {
    // After deposit + immediate full withdraw, user should get back <= deposit_amount
    // (never more, due to rounding favoring the pool)
    let shares = pool.deposit(deposit_amount);
    let withdrawn = pool.withdraw(shares);
    assert(withdrawn <= deposit_amount, 'ROUNDING_EXPLOIT');
}
```

### ERC-4626 Vault Share Manipulation

The same accumulator/precision attack from the zkLend exploit applies directly to ERC-4626 tokenized vaults (`ERC4626Component` in OZ Cairo 3.x). The first depositor can manipulate the share price by donating assets to inflate the exchange rate, causing subsequent depositors to receive fewer shares than expected.

**Two defense approaches:**

1. **Minimum liquidity lock** (described above) — the first depositor burns a small amount of shares to a dead address, establishing a baseline exchange rate that cannot be trivially inflated.
2. **Virtual shares/assets** — add 1 (or a small constant) to both the numerator and denominator in share calculations: `shares = (assets + 1) / (totalAssets + 1) * totalShares`. This eliminates the zero-denominator edge case and makes share price manipulation economically infeasible without requiring a liquidity lock. This is the approach used by OpenZeppelin's Solidity ERC-4626 implementation.

Both are valid; choose based on your protocol's constraints. Minimum liquidity lock is simpler but requires a one-time setup cost. Virtual shares are more elegant but require modifying the conversion math throughout.

### Wad Precision Truncation for Low-Decimal Tokens

*Source: [Code4rena Opus H-02 (Jan 2024)](https://code4rena.com/reports/2024-01-opus)*

Cairo's fixed-point `Wad` type (18 decimals) silently truncates when multiplied with tokens that have fewer decimals. In the Opus audit, `convert_to_yang_helper()` computed `(asset_amt * total_yang) / total_assets` — but because `Wad` multiplication divides by 1e18 internally, tokens with 8 decimals (like BTC) lost precision. A deposit of 0.0009 BTC ($36 at BTC=40K) resulted in **zero** shares.

**Rule:** When doing fixed-point math with tokens that have < 18 decimals, compute the numerator fully as `u256` before dividing. Never let intermediate `Wad` multiplication truncate low-decimal amounts to zero.

---

## 4. Cairo-Specific Pitfalls

*Source: [Cairo Book ch104](https://book.cairo-lang.org/ch104-01-general-recommendations.html)*

These are unique to Cairo and not found in Solidity auditing guides.

### Operator Precedence Bug

In Cairo, `&&` has higher precedence than `||`. Combined boolean expressions must be parenthesized.

```cairo
// BAD — && binds tighter than ||, so this means:
// mode == None || (mode == Recovery && coll_ok && debt_ok)
assert!(
    mode == Mode::None || mode == Mode::Recovery && ctx.coll_ok && ctx.debt_ok,
    "EMERGENCY_MODE"
);

// GOOD — explicit parentheses
assert!(
    (mode == Mode::None || mode == Mode::Recovery) && (ctx.coll_ok && ctx.debt_ok),
    "EMERGENCY_MODE"
);
```

### Unsigned Loop Underflow

Decrementing a `u32` counter past 0 panics. Use signed integers or explicit break.

```cairo
// BAD — panics when i decrements below 0
let mut i: u32 = n - 1;
while i >= 0 {  // always true for unsigned, then underflow panic
    process(i);
    i -= 1;
}

// GOOD — signed counter
let mut i: i32 = (n.try_into().unwrap()) - 1;
while i >= 0 {
    process(i.try_into().unwrap());
    i -= 1;
}
```

### Bit-Packing Overflow into felt252

Packing multiple fields into one `felt252` is common for gas optimization, but the sum of field sizes must not exceed 251 bits.

```cairo
// GOOD — explicit width checks before packing
fn pack_order(book_id: u256, tick_u24: u256, index_u40: u256) -> felt252 {
    assert!(book_id < (1_u256 * POW_2_187), "BOOK_OVER");
    assert!(tick_u24 < (1_u256 * POW_2_24), "TICK_OVER");
    assert!(index_u40 < (1_u256 * POW_2_40), "INDEX_OVER");
    let packed: u256 = (book_id * POW_2_64) + (tick_u24 * POW_2_40) + index_u40;
    packed.try_into().expect("PACK_OVERFLOW")
}
```

### `deploy_syscall(deploy_from_zero=true)` Collisions

Deterministic deployment from zero can collide if two contracts deploy with the same calldata. Set `deploy_from_zero` to `false` unless you specifically need deterministic addresses.

### Storage `Map.read()` Returns Zero for Non-Existent Keys (No Panic)

*Source: [Code4rena Starknet Perpetual H-01](https://code4rena.com/reports/2025-03-starknet-perpetual)*

Unlike languages that throw on missing keys, Cairo's `Map.read(key)` returns the type's default value (zero) when the key doesn't exist. This caused the H-01 finding in the Starknet Perpetual audit — an attacker used an arbitrary public key that mapped to empty storage, getting a zero value that bypassed oracle validation.

```cairo
// BAD — doesn't check if oracle exists, zero passes silently
let oracle_data = self.oracles.entry(asset_id).entry(public_key).read();
// oracle_data is 0 for non-existent keys — attacker signs over zeroed values

// GOOD — explicitly check for existence
let oracle_data = self.oracles.entry(asset_id).entry(public_key).read();
assert(oracle_data.is_non_zero(), 'ORACLE_NOT_REGISTERED');
```

**Rule:** Always validate that storage reads return non-default values when absence should be an error. This applies to all `Map` reads, not just oracle lookups.

### `get_caller_address().is_zero()` Is Useless

On Starknet, `get_caller_address()` is never the zero address (unlike Solidity's `msg.sender` for contract creation). Zero-address checks on caller are dead code.

### Unsafe `unwrap()` on User Input

*Source: [chipi-pay Nethermind AuditAgent finding #9](https://github.com/chipi-pay/sessions-smart-contract) — DoS via unsafe unwrap*

```cairo
// BAD — panics if conversion fails, exploitable DoS
let value: u64 = input.try_into().unwrap();

// GOOD — safe conversion
let value: u64 = match input.try_into() {
    Option::Some(v) => v,
    Option::None => { return 0; }  // safe failure, no panic
};
```

---

## 5. Signature Replay & Nonce Protection

*Source: [Oxor.io — Cairo Security Flaws (Aug 2024)](https://oxor.io/blog/2024-08-16-cairo-security-flaws/)*

Any signature-gated function without a nonce is replayable. An attacker can resubmit the same valid signature to execute the action multiple times.

### The Problem

```cairo
// BAD — no nonce, signature is replayable forever
fn claim_reward(
    ref self: ContractState, amount: felt252, r: felt252, s: felt252
) {
    let caller = get_caller_address();
    let msg = pedersen::pedersen(amount, caller.into());
    verify_ecdsa_signature(msg, self.signer.read(), r, s);
    // Transfer reward — attacker replays this with same (r, s) infinitely
    self._transfer_reward(caller, amount);
}
```

### The Fix — Always Include Nonce

```cairo
// GOOD — nonce prevents replay
fn claim_reward(
    ref self: ContractState, amount: felt252, r: felt252, s: felt252
) {
    let caller = get_caller_address();
    let nonce = self.nonces.read(caller);
    // Increment nonce BEFORE use (checks-effects-interactions)
    self.nonces.write(caller, nonce + 1);
    let msg = pedersen::pedersen(amount, caller.into());
    let msg_with_nonce = pedersen::pedersen(msg, nonce);
    verify_ecdsa_signature(msg_with_nonce, self.signer.read(), r, s);
    self._transfer_reward(caller, amount);
}
```

**Rule:** Every signature-verified operation must include (1) a nonce, (2) a chain_id, and (3) the contract address in the signed message to prevent cross-chain and cross-contract replay.

### Production Approach: OZ NoncesComponent

Don't roll your own nonce logic. Use OZ's `NoncesComponent`:

```cairo
use openzeppelin_utils::cryptography::nonces::NoncesComponent;

component!(path: NoncesComponent, storage: nonces, event: NoncesEvent);

#[abi(embed_v0)]
impl NoncesImpl = NoncesComponent::NoncesImpl<ContractState>;
impl NoncesInternalImpl = NoncesComponent::InternalImpl<ContractState>;

fn claim_reward(ref self: ContractState, amount: u256, nonce: felt252, signature: Span<felt252>) {
    let caller = get_caller_address();
    // Validates nonce is the next expected value AND increments it atomically
    self.nonces.use_checked_nonce(caller, nonce);
    // ... verify signature over (amount, nonce, chain_id, contract_address) ...
    self._transfer_reward(caller, amount);
}
```

`use_checked_nonce(owner, nonce)` verifies the nonce matches the expected next value and increments it in one step. `use_nonce(owner)` consumes and returns the current nonce without checking.

### SNIP-12: Typed Structured Data Signing

*Source: [OZ SNIP-12 Guide](https://docs.openzeppelin.com/contracts-cairo/3.x/guides/snip12), [SNIP-12 Spec](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md)*

SNIP-12 is Starknet's equivalent of EIP-712 — typed structured data signing that prevents signature reuse across contracts, chains, and message types. **Use SNIP-12 for all off-chain signature verification.**

```cairo
use openzeppelin_utils::cryptography::snip12;

// 1. Define your message struct
#[derive(Copy, Drop, Hash)]
struct Transfer {
    recipient: ContractAddress,
    amount: u256,
    nonce: felt252,
    expiry: u128,  // SNIP-12 has no u64, use u128 in type hash
}

// 2. Compute type hash offline and hardcode it
// starknet_keccak("Transfer(recipient:ContractAddress,amount:u256,nonce:felt,expiry:u128)")
const TRANSFER_TYPE_HASH: felt252 = 0x...; // Compute offline, don't do on-chain

// 3. Implement StructHash for your message type
// 4. Use OZ's OffchainMessageHash to compute the full hash including domain separator
```

**Key SNIP-12 security rules:**
- Domain separator MUST include `name`, `version`, `chain_id`, and `revision`
- **Breaking change:** Older revisions used `StarkNetDomain` (capital N), current uses `StarknetDomain` — mixing them produces different hashes
- Compute type hashes offline and hardcode them — on-chain computation is expensive and error-prone
- Always include a nonce and expiry in the message struct

---

## 6. Private Data in Storage

*Source: [FuzzingLabs — Top 4 Vulnerabilities (Nov 2024)](https://fuzzinglabs.com/top-4-vulnerability-cairo-starknet-smart-contract/)*

No data stored on Starknet is private. Any value written to contract storage is readable by anyone via RPC calls (`starknet_getStorageAt`). This includes "private" fields, passwords, API keys, and secrets.

```cairo
// BAD — secret is readable by anyone via RPC
#[storage]
struct Storage {
    secret: felt252,        // Anyone can read this
    admin_password: felt252, // This too
}

// GOOD — store hash, not plaintext
#[storage]
struct Storage {
    secret_hash: felt252,  // Store pedersen(secret) or poseidon(secret)
}

fn verify_secret(self: @ContractState, secret: felt252) -> bool {
    let hash = pedersen::pedersen(secret, 0);
    hash == self.secret_hash.read()
}
```

**Rule:** If your contract needs to verify a secret, store its hash on-chain and verify the preimage. Never store plaintext secrets, encryption keys, or passwords in contract storage.

---

## 7. felt252 Arithmetic & Safe Integer Types

*Source: [Oxor.io — Overflow and Underflow in Cairo](https://oxor.io/blog/2024-08-16-overflow-and-underflow-vulnerabilities-in-cairo/), [FuzzingLabs — Top 4 Vulnerabilities](https://fuzzinglabs.com/top-4-vulnerability-cairo-starknet-smart-contract/), [Crytic — Not So Smart Contracts](https://github.com/crytic/building-secure-contracts/tree/master/not-so-smart-contracts/cairo)*

The `felt252` type is a field element (0 to P-1, where P = 2^251 + 17*2^192 + 1). Arithmetic on `felt252` wraps modulo P silently — there is no overflow/underflow panic. This is the single most Cairo-specific footgun.

### The Problem

```cairo
// BAD — felt252 arithmetic wraps silently
fn vulnerable_subtract(balance: felt252, amount: felt252) -> felt252 {
    balance - amount  // If amount > balance, result wraps to a huge number (no panic!)
}

fn vulnerable_overflow(input: felt252) -> felt252 {
    let max_felt: felt252 = 0x800000000000000000000000000000000000000000000000000000000000000
        + 17 * 0x1000000000000000000000000000000000000000000000000;
    max_felt + input  // If input > 0, wraps to 0 (no panic!)
}
```

### The Fix — Use Safe Integer Types

Cairo's unsigned integer types (`u8`, `u16`, `u32`, `u64`, `u128`, `u256`) and signed types (`i8`, `i16`, `i32`, `i64`, `i128`) have built-in overflow/underflow protection. They panic on overflow, which is what you want.

```cairo
// GOOD — u256 panics on overflow/underflow
fn safe_subtract(balance: u256, amount: u256) -> u256 {
    assert(balance >= amount, 'INSUFFICIENT_BALANCE');
    balance - amount  // Would panic on underflow even without assert
}

// NOTE: In Cairo 2.x, plain a + b on integer types ALREADY panics on overflow.
// For non-panicking alternatives, use:
use core::num::traits::SaturatingAdd;
fn safe_add_saturating(a: u128, b: u128) -> u128 {
    a.saturating_add(b)  // Returns u128::MAX on overflow instead of panicking
}

// Or use overflowing_add for explicit overflow detection:
use core::integer::u128_overflowing_add;
fn safe_add_overflowing(a: u128, b: u128) -> (u128, bool) {
    match u128_overflowing_add(a, b) {
        Result::Ok(sum) => (sum, false),
        Result::Err(sum) => (sum, true),  // Overflow occurred
    }
}

// Or use wrapping_add for modular arithmetic (no panic, wraps):
use core::num::traits::WrappingAdd;
fn wrapping_add(a: u128, b: u128) -> u128 {
    a.wrapping_add(b)
}
```

### CRITICAL: `felt252` Division Is Field Division, NOT Floor Division

`felt252_div(a, b)` computes the **modular inverse**: it returns `n` such that `n * b ≡ a (mod P)`. This is NOT integer floor division. `felt252_div(10, 3)` does NOT return `3` — it returns a huge field element that, multiplied by 3 modulo P, equals 10.

```cairo
// BAD — gives completely wrong result for financial math
let price_per_unit: felt252 = felt252_div(total_cost, quantity);
// This is modular inverse, NOT 10/3 = 3

// GOOD — use integer division
let price_per_unit: u256 = total_cost / quantity;  // Floor division, panics on zero
```

**Rule:** NEVER use `felt252` for any division in financial calculations. Always use `u128`, `u256`, or `u64` which perform actual integer floor division. `felt252` division is only correct for cryptographic operations where you explicitly need modular arithmetic.

### When felt252 Is Acceptable

- **Hash computations** (pedersen, poseidon) — these are inherently modular arithmetic
- **Selectors and class hashes** — these are field elements by design
- **Storage keys** — addresses are felt252
- **Cryptographic operations** — signature verification, curve arithmetic

### When felt252 Is Dangerous

- **Balances, amounts, prices, fees** — always use `u256` or `u128`
- **Counters, indices, timestamps** — use `u64` or `u32`
- **Any user-controlled arithmetic** — never use felt252
- **Division** — `felt252_div` is modular inverse, not floor division
- **Comparisons** (`<`, `>`, `>=`) — felt252 comparisons work but can produce unexpected results near the field boundary

### Detecting felt252 Issues

Write targeted fuzz tests for functions that use felt252 arithmetic:

```cairo
#[test]
#[fuzzer(runs: 1000)]
fn fuzz_no_felt_underflow(a: felt252, b: felt252) {
    // If your function does a - b, test that the result is meaningful
    // Use u256 instead to get automatic underflow protection
    let safe_a: u256 = a.into();
    let safe_b: u256 = b.into();
    if safe_a >= safe_b {
        let result = safe_a - safe_b;
        assert(result <= safe_a, 'UNDERFLOW');
    }
}
```

> **Note:** FuzzingLabs' `sierra-analyzer` had a `felt_overflow` detector but the repo is no longer maintained. Until a replacement ships, fuzz testing is the primary detection method.

---

## 8. Storage Layout Security

*Source: [Starknet Docs — Storage](https://docs.starknet.io/build/starknet-by-example/basic/storage), [Cairo Book — Security (ch104)](https://book.cairo-lang.org/ch104-00-starknet-smart-contracts-security.html)*

Starknet storage is a flat key-value space of 2^251 slots, each holding one `felt252`. Understanding this model is critical for upgrade safety and collision avoidance.

### Storage Address Derivation

```
// Simple variables: base = sn_keccak("variable_name")
// Map entries: address = pedersen(sn_keccak("map_name"), key)
// Nested maps: address = pedersen(pedersen(sn_keccak("map_name"), key1), key2)
// Component storage: base = sn_keccak("component_name") (with substorage(v0))
```

### `LegacyMap` → `Map` Migration (Cairo 2.7+)

Cairo 2.7.0 introduced `Map<K, V>` (from `core::starknet::storage::Map`) to replace `LegacyMap<K, V>`. The storage layout is identical, so migration is safe for upgradeable contracts. `LegacyMap` is deprecated but still compiles on current Cairo versions — it emits a deprecation warning, not an error. Projects on older Scarb versions that cannot upgrade immediately can defer this migration, but should plan for it: `LegacyMap` may be removed in a future Cairo edition.

```cairo
// DEPRECATED — LegacyMap (Cairo < 2.7)
#[storage]
struct Storage {
    balances: LegacyMap<ContractAddress, u256>,
}
// Access: self.balances.read(addr), self.balances.write(addr, val)

// CURRENT — Map (Cairo 2.7+)
use core::starknet::storage::Map;
#[storage]
struct Storage {
    balances: Map<ContractAddress, u256>,
}
// Access: self.balances.entry(addr).read(), self.balances.entry(addr).write(val)
```

### Storage Nodes and `Vec` (Cairo 2.7+)

Cairo 2.7+ introduced `#[starknet::storage_node]` for composable nested storage and `Vec` for dynamic-length storage arrays:

```cairo
use core::starknet::storage::Vec;

// Storage Node — structured nested storage
#[starknet::storage_node]
struct UserData {
    balance: u256,
    last_active: u64,
}

#[storage]
struct Storage {
    users: Map<ContractAddress, UserData>,   // Nested: users.entry(addr).balance.read()
    pending_items: Vec<u256>,                 // Dynamic array in storage
}
```

**Security note for Vec:** `Vec` has no built-in length cap. User-growable Vecs can be used for DoS via unbounded storage growth. Always cap Vec length in user-facing functions.

### Storage Collision Between Components

If two components use the same storage variable name, their base addresses will collide. OZ's `#[substorage(v0)]` pattern avoids this for components, but custom storage vars can still collide.

```cairo
// BAD — two components both define a storage var named "balance"
// They will write to the same slot and corrupt each other's data

// GOOD — use unique prefixed names or rely on OZ component patterns
#[storage]
struct Storage {
    #[substorage(v0)]
    erc20: ERC20Component::Storage,       // OZ handles namespacing
    #[substorage(v0)]
    ownable: OwnableComponent::Storage,   // No collision with erc20
    my_custom_balance: u256,              // Explicit, unique name
}
```

**Storage Node collision note:** Storage nodes hash member names with `selector!("name")`. Two unrelated storage nodes with the same member name in different contexts won't collide because the parent path differs. However, custom `Store` implementations that pack data into raw slots bypass this namespacing.

### Upgrade Storage Layout Rules

When upgrading a contract (replacing the class hash), storage persists but layout must be compatible:

```
SAFE:
  - Add new storage variables (new base addresses)
  - Append new fields to the end of packed structs
  - Add new component substorages
  - Migrate LegacyMap to Map (same layout)

UNSAFE (will corrupt existing data):
  - Remove or reorder existing storage variables
  - Change the type of an existing variable (e.g., u128 -> u256)
  - Rename a storage variable (changes base address)
  - Change a component's substorage name
  - Change Map key types
```

### Multi-Slot Values

Types larger than 252 bits (e.g., `u256`) span consecutive slots. A `u256` uses slot `base + 0` for the low 128 bits and `base + 1` for the high 128 bits. Packing multiple small values into one `felt252` is a gas optimization but must respect the 251-bit limit (see Section 4, Bit-Packing).

---

## 9. Token Integration Pitfalls

*Source: [Cairo Book ch104](https://book.cairo-lang.org/ch104-01-general-recommendations.html)*

### Always Check Boolean Returns

While OpenZeppelin's ERC20 reverts on failure, not all ERC-20 implementations do. Some return `false` without panicking.

```cairo
// BAD — ignores return value
IERC20Dispatcher { contract_address: token }.transfer(to, amount);

// GOOD — check the return
let success = IERC20Dispatcher { contract_address: token }.transfer(to, amount);
assert(success, 'Transfer failed');
```

### CamelCase / snake_case Dual Interfaces

Most ERC20 tokens on Starknet use `snake_case`. Legacy tokens may have `camelCase` entrypoints (`transferFrom` vs `transfer_from`). If your contract interacts with arbitrary tokens, handle both or verify the tokens you'll integrate with.

### ERC20Permit — Off-Chain Approval Attack Surface

OZ Cairo 3.x added `ERC20Permit`, enabling token `approve` via off-chain SNIP-12 signatures. This is a new attack surface:

- **Front-running:** Permit signatures can be front-run — someone sees the permit in the mempool and submits it first. The standard handles this gracefully (the approve succeeds if allowance matches), but protocols should not assume permit calls are exclusive.
- **Expired permits:** Always check the deadline/expiry. A signed permit with a far-future expiry is a long-lived approval.
- **Nonce correctness:** Permit uses the owner's nonce from `NoncesComponent`. A consumed nonce invalidates the permit.
- **Integration rule:** When integrating with permit-enabled tokens, accept that `permit` + `transferFrom` may happen atomically in one call or separately. Don't rely on the approval being set in a previous transaction.

---

## 10. L1/L2 Bridging Safety

*Source: [Crytic/building-secure-contracts](https://github.com/crytic/building-secure-contracts/tree/master/not-so-smart-contracts/cairo)*

### L1 Handler Must Validate Caller

The `#[l1_handler]` attribute marks an entrypoint as callable from L1. Always validate that `from_address` is the trusted L1 contract.

> **Type note:** `from_address` in `#[l1_handler]` is `felt252`, NOT `ContractAddress`. This is a common source of bugs — you cannot use `ContractAddress` comparison directly. Compare as `felt252` or convert explicitly.

```cairo
// BAD — anyone on L1 can call this
#[l1_handler]
fn handle_deposit(
    ref self: ContractState,
    from_address: felt252,
    account: ContractAddress,
    amount: u256
) {
    self.balances.write(account, self.balances.read(account) + amount);
}

// GOOD — validate L1 caller
// NOTE: from_address is felt252, NOT ContractAddress.
// Store your L1 bridge address as felt252 to match, or convert explicitly.
#[l1_handler]
fn handle_deposit(
    ref self: ContractState,
    from_address: felt252,  // felt252 — not ContractAddress!
    account: ContractAddress,
    amount: u256
) {
    let l1_bridge: felt252 = self.l1_bridge.read(); // stored as felt252
    assert!(!l1_bridge.is_zero(), "UNINIT_BRIDGE");
    assert!(from_address == l1_bridge, "ONLY_L1_BRIDGE");
    self.balances.write(account, self.balances.read(account) + amount);
}
```

### L1-to-L2 Message Failure

L1->L2 messages can fail silently if the L2 handler reverts. The message stays in a "pending" state and can be retried, but the L1 side may have already updated its state. Design for idempotent handlers or include replay protection.

### L1/L2 Address Conversion

L1 (Ethereum) addresses are 20 bytes. Starknet addresses are felt252. Incorrect conversion or comparison between the two is a common bug. Always use explicit conversion functions and never compare raw values across domains.

### Replay Protection

Cross-chain messages need nonces or unique identifiers to prevent replay. If a message can be re-consumed, an attacker can double-credit.

### Bridge Withdrawal Limits (StarkGate Pattern)

*Source: [StarkGate 2.0 `token_bridge.cairo`](https://github.com/starknet-io/starkgate-contracts), [Starknet Docs — StarkGate](https://docs.starknet.io/learn/protocol/starkgate)*

StarkGate implements a daily withdrawal limit of 5% TVL per token (`DEFAULT_DAILY_WITHDRAW_LIMIT_PCT = 5`). A `SECURITY_AGENT` role can freeze withdrawals; lifting the freeze requires a quorum of `SECURITY_ADMIN` signers. This pattern limits damage from exploits to a single day's quota.

**Rule:** Any bridge or high-TVL vault should implement per-token daily withdrawal caps, a security freeze role (single key, fast response), and a multi-sig requirement to unfreeze. Do not let a single key both freeze and unfreeze.

### Unprotected Escrow Funds (MakerDAO DAI Bridge)

*Source: [ChainSecurity — MakerDAO StarkNet-DAI-Bridge Audit (2021)](https://chainsecurity.com/wp-content/uploads/2021/12/ChainSecurity_MakerDAO_StarkNet-DAI-Bridge_audit.pdf)*

ChainSecurity found a Critical finding in the MakerDAO StarkNet-DAI-Bridge: escrow funds on L1 were unprotected, allowing unauthorized access. The audit covered both Solidity L1 contracts and the Cairo L2 `dai.cairo` contract. Additional findings included 1 High, 5 Medium, and 5 Low — all fixed.

**Pattern:** L1/L2 bridges must protect escrowed funds on both sides. The L1 escrow is only as safe as the L2 handler validation, and vice versa.

---

## 11. Economic / DoS Patterns

*Source: [Cairo Book ch104](https://book.cairo-lang.org/ch104-01-general-recommendations.html)*

### Unbounded Loops

User-controlled iterations can exceed the Starknet steps limit, bricking the contract permanently — no one can interact with it anymore.

```cairo
// BAD — unbounded loop, attacker grows the list to exceed step limit
fn process_all(ref self: ContractState) {
    let mut i = 0;
    let count = self.pending_count.read();
    while i < count {
        self._process(i);
        i += 1;
    }
}

// GOOD — pagination pattern with bounded iterations
fn process_batch(ref self: ContractState, start: u64, max: u64) -> u64 {
    let mut i = start;
    let end = core::cmp::min(self.pending_count.read(), start + max);
    while i < end {
        self._process(i);
        i += 1;
    }
    end  // return next cursor
}
```

### Bad Randomness

Never use `block_timestamp`, `block_number`, or transaction hashes as randomness sources. They are known to validators/sequencers before execution. Use Pragma VRF or similar oracle-based randomness.

### Pause Mechanism — Don't Pause Liquidations

*Source: [Code4rena Starknet Perpetual L-05](https://code4rena.com/reports/2025-03-starknet-perpetual)*

When implementing `PausableComponent`, do NOT apply `assert_not_paused()` to liquidation or risk-management functions. Blocking liquidations during an emergency pause compounds the crisis — insolvent positions can't be closed, leading to bad debt accumulation.

```cairo
// BAD — pause blocks liquidation
fn liquidate(ref self: ContractState, position_id: u64) {
    self.pausable.assert_not_paused(); // Blocks during emergency!
    self._liquidate(position_id);
}

// GOOD — liquidation always available, other functions paused
fn open_position(ref self: ContractState, ...) {
    self.pausable.assert_not_paused(); // Paused during emergency
    // ...
}

fn liquidate(ref self: ContractState, position_id: u64) {
    // No pause check — must always be available
    self._liquidate(position_id);
}
```

---

## 12. Account Abstraction Security

*Source: [Starknet Docs — Account Abstraction](https://docs.starknet.io/build/starknet-by-example/advanced/account-abstraction)*

Starknet's native account abstraction means every account is a smart contract with `__validate__` and `__execute__` entry points. This is a unique attack surface.

### `__validate__` Constraints

`__validate__` runs before `__execute__` and has strict constraints:
- **Limited gas** — cannot perform expensive computation
- **Cannot modify storage** (except the nonce)
- **If `__validate__` fails, the sequencer loses gas** — no fee is charged to the account, but the sequencer still consumed resources for the validation attempt. This is a practical gotcha when testing custom accounts: failed validations cost the network real gas but produce no state changes or receipts.
- Must return `VALID` (felt252 value of `'VALID'`) or the transaction is rejected

### Sequencer DoS via `__validate__`

A malicious account can implement `__validate__` to always succeed initially but fail on re-execution (after the sequencer has committed gas). This griefs sequencers. Mitigation is sequencer-side (reputation systems, deposit requirements), but be aware when deploying custom account contracts.

### Custom Account Security Rules

```cairo
// Required entrypoints for an account contract
#[abi(embed_v0)]
fn __validate__(ref self: ContractState, calls: Array<Call>) -> felt252 {
    // 1. Verify signature (MUST be fast and cheap)
    // 2. Validate nonce (handled by protocol, but check custom logic)
    // 3. Do NOT make external calls
    // 4. Do NOT write to storage (except nonce)
    starknet::VALIDATED  // Return 'VALID'
}

#[abi(embed_v0)]
fn __execute__(ref self: ContractState, calls: Array<Call>) -> Array<Span<felt252>> {
    // 1. Assert caller is the protocol (assert_only_protocol)
    // 2. Verify correct tx version
    // 3. Execute calls
    // 4. Emit TransactionExecuted event
    execute_multicall(calls.span())
}
```

---

## 13. Session Key Security

*Source: [chipi-pay SNIP draft and Nethermind AuditAgent findings](https://github.com/chipi-pay/sessions-smart-contract) — 18 findings across 4 scans*

For contracts implementing session key delegation (relevant to AI agents):

### Admin Selector Blocklist

Session keys MUST NOT be able to call privileged functions. Each of these was discovered in a separate Nethermind audit scan:

```cairo
const BLOCKED_SELECTORS: [felt252; 7] = [
    selector!("upgrade"),                   // scan 1: contract replacement
    selector!("add_or_update_session_key"), // scan 1: create unrestricted sessions
    selector!("revoke_session_key"),        // scan 1: revoke other sessions
    selector!("__execute__"),               // scan 2: nested execution privilege escalation
    selector!("set_public_key"),            // scan 3: owner key rotation (OZ PublicKeyImpl)
    selector!("setPublicKey"),              // scan 3: owner key rotation (OZ PublicKeyCamelImpl)
    selector!("execute_from_outside_v2"),   // scan 3: nested SNIP-9 double-consumption
];
```

**Key lesson:** The denylist approach is inherently fragile — each audit scan found new selectors. Prefer the self-call block (below) as the primary defense.

### Self-Call Block (Primary Defense)

Block ALL calls where `call.to == get_contract_address()` when the session has no explicit whitelist. This eliminates the entire class of privilege escalation via self-calls, protecting against any future OZ embedded impl exposing new privileged selectors.

```cairo
// In validation, when allowed_entrypoints_len == 0:
for call in calls {
    assert(call.to != get_contract_address(), 'SESSION_NO_SELF_CALL');
}
```

> **OZ version note:** OZ 3.x `AccountComponent` has evolved its embedded impls and selectors compared to earlier versions. The exact set of privileged selectors exposed may differ between OZ v0.x, v2.x, and v3.x. The self-call block pattern above remains the primary defense regardless of OZ version, because it protects against the entire class of privilege escalation without enumerating specific selectors.

### Spending Limits (Value Control)

Selector whitelists control *which functions* a session can call, but not *how much value* each call moves. A session authorized to call `transfer` can transfer the entire balance.

```cairo
struct SpendingPolicy {
    token_address: ContractAddress,
    max_amount_per_call: u256,
    max_amount_per_window: u256,    // rolling window cap
    window_seconds: u64,             // e.g., 86400 = 24h
    amount_spent_in_window: u256,
    window_start: u64,
}
```

**Why rolling window instead of total cap?** A total cap (`max = 100 USDC`) doesn't protect against burst attacks — the attacker drains it in one call. A rolling window (`max 10 USDC per 24h`) limits damage even if the key is compromised for days.

### Call Consumption Ordering

*Source: chipi-pay Nethermind scan 2, finding #3*

Increment `calls_used` AFTER signature verification, not before. Otherwise a session with `max_calls = 1` fails on its first valid use because the counter was incremented before the limit check runs.

### `is_valid_signature` Has No Call Context

*Source: chipi-pay Nethermind scan 1, finding #5*

`is_valid_signature(hash, signature)` receives only hash and signature — no calls. It cannot enforce selector whitelists. Enforce whitelists in `__validate__` and `execute_from_outside_v2` where calls are available. This is an inherent ERC-1271 limitation, not a bug.

---

## 14. SNIP-9 Outside Execution Security

*Source: [SNIP-9 Spec](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-9.md), [Starknet.js Outside Execution Guide](https://starknetjs.com/docs/guides/outsideExecution)*

SNIP-9 enables meta-transactions: a third party submits transactions on behalf of an account using the account's signature. This is a major attack surface.

### How It Works

An `OutsideExecution` object contains:
- `caller` — who is allowed to submit (or `'ANY_CALLER'` for anyone)
- `execute_after` / `execute_before` — time window
- `nonce` — dedicated outside-execution nonce (separate from tx nonce)
- `calls` — the actual calls to execute

The account signs this typed data (SNIP-12 format), and any permitted caller can submit it within the time window.

### Security Rules

```cairo
// 1. ALWAYS validate and consume the outside-execution nonce
// The nonce is separate from the normal transaction nonce.
// If not consumed, the same signed payload can be replayed.
assert(!self.outside_nonces.read(nonce), 'NONCE_ALREADY_USED');
self.outside_nonces.write(nonce, true);

// 2. Validate caller
let outside_caller = outside_execution.caller;
if outside_caller != 'ANY_CALLER'.try_into().unwrap() {
    assert(get_caller_address() == outside_caller, 'INVALID_CALLER');
}

// 3. Validate time bounds
let now = get_block_timestamp();
assert(now > outside_execution.execute_after, 'TOO_EARLY');
assert(now < outside_execution.execute_before, 'TOO_LATE');
```

### Common Vulnerabilities

- **Missing nonce consumption** — allows unlimited replay of a signed outside execution within the time window
- **Overly permissive `ANY_CALLER`** — anyone can submit the transaction, not just the intended relayer
- **Wide time windows** — `execute_before` set too far in the future gives attackers more time to replay
- **Nested reentrancy** — `execute_from_outside` calling `__execute__` calling `execute_from_outside` again. Block this with ReentrancyGuard or explicit nesting checks
- **Missing SNIP-12 domain binding** — signatures must include `chain_id` and contract version to prevent cross-chain replay

### Interaction with Session Keys

When combining SNIP-9 with session keys (Section 13), the session key's `execute_from_outside_v2` selector should be in the blocklist to prevent a session key from creating nested outside executions that bypass call limits.

---

## 15. Starknet Protocol Security Considerations

*Source: [Starknet Version Notes](https://docs.starknet.io/learn/cheatsheets/version-notes), [Starknet v0.14.0 "Grinta" Announcement](https://starknet.io/blog/starknet-grinta-the-architecture-of-a-more-decentralized-future)*

### Starknet v0.14.0 "Grinta" (Sep 1, 2025) — Breaking Changes

Grinta introduced multi-sequencer architecture (three independent sequencers with Tendermint consensus), a mempool, fee market, and subsecond pre-confirmations.

**Breaking changes that affect deployed contracts:**

- **v0, v1, v2 transactions are no longer supported.** Any contracts or tooling relying on legacy transaction types will fail. Accounts that lack `__validate__` must be called via the new `meta_tx_v0` syscall through v3 transactions. This is a hard break.
- **Block time shortened from ~30s to ~6s.** All time-dependent logic (funding rate calculations, price staleness windows, oracle freshness checks, time-locked operations) must be recalibrated. A 10-block window went from ~5 minutes to ~1 minute.
- **L2 gas fee market (EIP-1559 style).** L2 gas now has a dynamic base price. Contracts that estimate or hardcode gas costs will be wrong. Use current pricing from `get_execution_info`.
- **Transactions with internal calls to `__execute__` are reverted.** If your contract makes calls to an entry point literally named `__execute__`, those transactions will revert under v0.14.0.

**Security implications:**

- **MEV risk** — with a mempool and multiple sequencers, transaction ordering is no longer deterministic. Contracts sensitive to execution ordering (DEXes, liquidations) must implement slippage protection and deadline checks
- **Sequencer reorgs** — the Sep 2, 2025 incident showed reorgs are possible when sequencers diverge. Design for idempotent operations where possible
- **L1 handler failures** — failed L1 handlers are now included as `REVERTED` in blocks (bounded execution resources). Contracts relying on L1 handlers must handle reverts gracefully

### Sequencer-Prover Inconsistency (Zellic/Starknet OS Audit)

*Source: [Starknet Community Forum — Remediating a potential sequencer-prover inconsistency](https://community.starknet.io/t/remediating-a-potential-sequencer-prover-inconsistency-in-the-cairo-vm/115313)*

Zellic auditor @fcremo discovered an opcode with different validation logic between the RustVM (sequencer) and the Cairo AIR (prover). A transaction that passed sequencer validation could fail proof verification, or vice versa. StarkWare patched this as an immediate fix in v0.13.3. LambdaClass confirmed the impact on their VM implementation.

**This is a novel vulnerability class unique to STARK-based systems.** Contracts themselves cannot cause or prevent it, but developers should know: the trust model assumes sequencer and prover agree on all execution semantics. If they diverge, valid-looking transactions can fail at proof time, or invalid ones could pass sequencing. This is why Cairo VM formal verification (see Sources) matters.

### Starknet v0.14.1 (Dec 2025) — BLAKE Hash Migration

v0.14.1 migrated from Poseidon to BLAKE hash functions for `compiled_class_hash` computation. If your contract or tooling computes or verifies class hashes, ensure you use the correct hash function for the target Starknet version.

### V3 Transaction Resource Bounds

Since v0.13.0, V3 transactions use separate `L1_GAS` and `L2_GAS` resource bounds instead of a single `max_fee`. Contracts that validate or limit fees (e.g., account contracts during escapes) must check both resource types. See the ConsenSys Argent finding in Section 16 for a real example of this bug.

### Paymaster Security Considerations

Starknet's fee abstraction (via AVNU paymaster, Cartridge paymaster, etc.) allows third parties to pay gas on behalf of users. Security considerations:

- **Griefing:** A malicious account can pass `__validate__` but intentionally fail `__execute__`, wasting the paymaster's gas. Paymasters should implement reputation systems or require pre-deposits.
- **Token-based paymasters** must lock the user's payment tokens (e.g., ERC20) BEFORE paying gas, or the user can drain the paymaster by failing after gas is consumed.
- **Allowlists:** Production paymasters should maintain a whitelist of approved contracts/entrypoints to prevent abuse.
- **Rate limiting:** Per-account or per-session rate limits prevent a single agent from exhausting the paymaster's gas budget.

---

## 16. Real Audit Findings Reference

### zkLend Exploit — $10M Loss (February 12, 2025)

*Source: [BlockSec Post-Mortem](https://blocksec.com/blog/zklend-exploit-post-mortem), [SolidityScan Analysis](https://blog.solidityscan.com/zklend-hack-analysis), [FuzzingLabs](https://fuzzinglabs.com/rediscovery-zklend-hack/)*

- **Root cause:** Precision loss through truncation in `safe_decimal_math` division, combined with accumulator manipulation via flash loan donation mechanism in an empty market.
- **Impact:** Attacker inflated collateral from 1 wei to 7,015 wstETH, then borrowed other assets. Stolen funds bridged to Ethereum and attempted laundering via Railgun.
- **Recovery:** Railgun's compliance policies partially blocked the laundering attempt. The attacker later sent an on-chain message to zkLend and partial fund recovery negotiations followed. Not all funds were recovered.
- **Key lesson:** Empty market initialization + flash loan donation + floor division = catastrophic precision exploit. Detectable with fuzz tests targeting deposit/withdraw invariants.
- **Mitigation:** Minimum liquidity lock on first deposit, accumulator change caps, round-up on burns. (See Section 3 above.)

### CVE-2024-45304 — OpenZeppelin Cairo Ownership Bug

OZ Cairo Contracts before v0.16.0: `renounce_ownership` could be used to transfer ownership unintentionally. Fixed in v0.16.0.

### ConsenSys Diligence — Argent Account Starknet V3 (Jan 2024)

*Source: [ConsenSys Diligence Report](https://diligence.consensys.io/audits/2024/01/argent-account-argent-multisig-starknet-transaction-v3-updates/)*

- **Major — Lack of Fee Limits for V3 Transactions:** V3 transactions introduced separate L1_GAS and L2_GAS resource bounds, but only the `tip` was capped for escape transactions. A malicious Guardian could set excessive `max_price_per_unit` on L1_GAS or L2_GAS to drain the account. Fixed by introducing `MAX_ESCAPE_MAX_FEE_STRK = 50 STRK` and `MAX_ESCAPE_TIP_STRK = 1 STRK`.
- **Minor — `__validate_deploy__` reused `assert_correct_invoke_version`:** Deploy transactions should have their own version check for better maintainability and correctness.
- **Minor — `OUTSIDE_EXECUTION_TYPE_HASH` comment mismatch:** Hardcoded hash constant was correct but comments described the wrong preimage string.
- **Minor — Self-written `get_execution_info`:** Duplicate implementations of stdlib functions. Use `starknet::info` module directly.

**Pattern:** When Starknet introduces new transaction versions, all fee-limiting logic must be reviewed. Fee caps that work for V1 may not cover V3 resource bounds.

### Code4rena Starknet Perpetual (Mar–Apr 2025) — 2 High, 3 Medium, 14 Low

*Source: [Code4rena Report](https://code4rena.com/reports/2025-03-starknet-perpetual), 39 Cairo contracts, 3,846 lines*

- **H-01 — Malicious signed price injection:** `_validate_oracle_signature` reads `asset_oracle` storage for a public key but doesn't panic on non-existent key (returns zero). Attacker generates signature over zeroed-out packed values and injects arbitrary price. Fix: panic if `packed_asset_oracle` is zero.
- **H-02 — `_execute_transfer` wrong order of operations:** State diff applied before health check, so the check re-applies the diff and rejects valid transfers. Classic checks-effects-interactions violation.
- **M-01 — Deleveragable positions can't be fully liquidated:** When a position is fully liquidated (`TR == 0`), the `assert_healthy_or_healthier` check panics on `total_risk.is_zero()`, blocking liquidation of insolvent positions.
- **M-02 — Liquidatable positions forced into opposite:** Long positions can be forced into short during liquidation and vice versa.
- **M-03 — Stale prices in `funding_tick()`:** Inactive price data used for funding calculations.
- **L-03 — Owner account overwrite:** Missing validation allows owner to be overwritten without proper authorization flow.
- **L-04 — Missing curve validation for public keys:** Public keys in `new_position` not validated against the Stark curve. Always verify that public keys lie on the STARK curve; an invalid key creates permanently unusable state.
- **L-05 — Liquidation blocked by pause:** Applying `assert_not_paused()` to liquidation blocked risk management during emergencies. Liquidation must always be available. (See Section 11, "Pause Mechanism — Don't Pause Liquidations.")
- **L-06 — Global validation DoS:** `validate_assets_integrity()` checks ALL active assets, blocking operations on unrelated assets when one has stale data. (See Section 18, "DeFi Protocol Security Patterns.")
- **L-07 — Stale prices for inactive assets:** Inactive assets can't have prices updated but their last price is still used for settlement with no freshness check.
- **L-08 — Collateral-only users blocked:** Users with zero synthetic exposure blocked from withdrawing collateral because global validation ran unconditionally.

**Pattern:** Storage reads that return default values (zero) on missing keys are a Cairo-specific footgun. Always explicitly check that a storage read returned a non-default value.

### Code4rena Opus (Jan 2024) — 4 High, 9 Medium

*Source: [Code4rena Opus Report](https://code4rena.com/reports/2024-01-opus), 15 Cairo contracts, 4,056 lines*

The first major Cairo DeFi competitive audit on Code4rena. Key findings:

- **H-02 — Wad precision truncation for low-decimal tokens:** `convert_to_yang_helper()` lost precision for tokens with < 18 decimals due to intermediate Wad multiplication truncating to zero. A BTC deposit worth $36 resulted in 0 shares. (See Section 3, "Wad Precision Truncation.")
- **H-03 — Redistribution array index mismatch:** `redistribute_helper` maintained two arrays (`updated_trove_yang_balances` and `new_yang_totals`) but a `continue` statement caused them to go out of sync. Attacker could keep collateral while having debt redistributed away — debt zeroed, yangs kept.
- **H-04 — Recovery mode manipulation within single transaction:** Attacker opens a large enough position to push the system into recovery mode, which lowers liquidation thresholds, then liquidates healthy troves — all in one tx. Flash-loan amplifiable.
- **M-03 — ERC-4626 inflate mitigation insufficient:** The first-depositor share inflation attack was not fully mitigated, reinforcing the need for minimum liquidity locks.

**Pattern:** DeFi protocols using custom fixed-point types (Wad, Ray) must test with tokens of varying decimals. Array synchronization bugs in loop-with-continue are a Cairo-specific code smell. Recovery mode / global state changes must not be triggerable and exploitable within a single transaction.

### ChainSecurity — Starknet Perpetual (2025)

*Source: [ChainSecurity Report](https://www.chainsecurity.com/security-audit/starkware-starknet-perpetual)*

Independent audit alongside Code4rena. Key findings:

- **Rounding Is Not Always in Favor of the System:** Arithmetic rounding in settlement/funding calculations sometimes favored the user instead of the protocol, allowing slow value extraction over many transactions.
- **Insurance Fund Cannot Always Be the Deleverager:** Edge cases where the insurance fund could not fulfill its role as deleverager for insolvent positions.
- **Loosely Restricted Liquidations:** Operator had more latitude than documented to execute liquidations.

**Pattern:** Always round in favor of the protocol/pool/system, never the user. Verify this direction for every division in financial math. Insurance fund/backstop logic must handle edge cases (zero balance, concurrent liquidations).

### chipi-pay Session Contract — 18 Findings, 4 Nethermind Scans

*Source: [chipi-pay/sessions-smart-contract](https://github.com/chipi-pay/sessions-smart-contract)*

- Scan 1: 10 findings (3 High — unrestricted `__execute__` caller, whitelist bypass in `is_valid_signature`, call-limit bypass via `calls_used` reset)
- Scan 2: 3 findings (1 High — nested `__execute__` privilege escalation)
- Scan 3: 5 findings (2 High — `set_public_key`/`setPublicKey` not in blocklist)
- Scan 4: 0 findings — clean report after self-call block + expanded blocklist

**Pattern:** Every scan found new privileged selectors exposed by OZ embedded implementations. The self-call block (scan 4) eliminated the entire vulnerability class.

### Nethermind Public Cairo/Starknet Audit Catalogue

*Source: [NethermindEth/PublicAuditReports](https://github.com/NethermindEth/PublicAuditReports)*

Nethermind has published 25+ Cairo/Starknet-specific audit reports covering core ecosystem protocols:

| Report | Protocol | Focus |
|--------|----------|-------|
| NM0050, NM0064 | StarkGate | L1/L2 token bridge |
| NM0052 | Argent Account | Starknet smart wallet |
| NM0054 | Aave L2 | Lending protocol on Starknet |
| NM0056, NM0120 | ZKX | Perpetual DEX |
| NM0058, NM0097, NM0161, NM0392, NM0462 | zkLend | Lending, zkToken, liquid staking, recovery |
| NM0060 | MySwap / Braavos | AMM / wallet |
| NM0061 | Cartridge | Gaming account |
| NM0135 | Starknet ID | Identity / naming |
| NM0141, NM0578 | AVNU | DEX aggregator, forwarder |
| NM0147 | Pragma | Oracle network |
| NM0153 | Carmine | Options protocol |
| NM0159 | Dojo | Gaming engine |
| NM0180 | JediSwap | AMM |
| NM0194 | Starknet Token Distributor | STRK distribution |
| NM0237 | LayerAkira | Order book DEX |
| NM0259 | Starknet Nova | Core protocol |
| NM0337 | StakeStark | STRK staking |
| NM0544A, NM0544B | Piltover, Token Bridge | Core Starknet bridge |

**Use these as reference when building similar protocol types.** Each report PDF is available at the Nethermind repo.

### Code4rena LayerZero Starknet Endpoint (Oct–Nov 2025) — 0 High, 0 Medium, 6 Low

*Source: [Code4rena Report](https://code4rena.com/reports/2025-10-layerzero-starknet-endpoint), 46 Cairo files*

Cross-chain messaging endpoint in Cairo. No H/M findings, but Low findings contain useful patterns:

- **L-02 — Allowance-sweeping refund DoS:** `_refund_native()` tried to refund `allowance - fee` instead of `min(allowance - fee, balance)`. Users with standard large ERC20 approvals couldn't send messages. **Pattern:** When refunding excess tokens via `transferFrom`, cap the refund to `min(excess, sender_balance)`. Never assume `balance >= allowance`.
- **L-03 — Nilified messages re-committable:** `commit()` could overwrite `NIL_PAYLOAD_HASH` because `_has_payload_hash()` only checked `!= EMPTY_PAYLOAD_HASH`. **Pattern:** State invalidation (nilification/burning/blacklisting) must be checked explicitly before any state overwrite. Don't rely on "not empty" as a proxy for "valid."

### Cairo Security Clan — 30+ Cairo Audit Reports

*Source: [Cairo-Security-Clan/Audit-Portfolio](https://github.com/Cairo-Security-Clan/Audit-Portfolio)*

Starknet-native audit firm with 30+ public Cairo audit PDFs covering major ecosystem protocols:

| Protocol | Report |
|----------|--------|
| Ekubo | `Ekubo_Audit_Report.pdf` |
| Vesu | 7 reports (Core, Extensions, Liquidate, Multiply, Periphery, Updates) |
| Opus | `Opus_Audit_Report.pdf` |
| Paradex | `Paradex_Audit_Report.pdf` |
| Hyperlane | `Hyperlane_Audit_Report.pdf` + update |
| Clober | `Clober_Audit_Report.pdf` |
| Layer Akira | `Layer_Akira_Audit_Report.pdf` |
| Nimbora | `Nimbora Audit Report.pdf` |
| Nostra Pools | `Nostra Pools Security Review by 0xerim.pdf` |
| AVNU DCA | `Avnu_DCA_Audit_Report.pdf` |
| Argent Gifting | `Argent_Gifting_Audit_Report.pdf` |
| Starknet ID | `Starknet_ID_Audit_Report.pdf` |

**Use these as reference when building similar protocol types.** All PDFs are in the GitHub repo.

### ChainSecurity — Vesu Protocol (2024)

*Source: [ChainSecurity Report](https://www.chainsecurity.com/security-audit/vesu-protocol-smart-contracts)*

Permissionless DeFi lending protocol audit. All issues were fixed, but ChainSecurity noted **elevated residual risk** due to project complexity and limited internal QA (single developer). Key covered areas: pool isolation, asset solvency, oracle security, access control.

**Pattern:** For complex DeFi protocols, a single audit is not sufficient. ChainSecurity explicitly flagged that novel issues and regressions appeared during the last review cycle despite earlier fixes. Budget for multiple audit cycles and invest in internal security-focused QA (thorough unit/regression testing).

---

## 17. OpenZeppelin Cairo Security Components

*Source: [OZ Cairo 3.x Security Docs](https://docs.openzeppelin.com/contracts-cairo/3.x/security)*

OZ Cairo provides core security components. Use them instead of rolling your own.

> **OZ v3.0.0 Import Path Migration (breaking):** In v3.0.0, `execute_single_call`, `execute_calls`, and `assert_valid_signature` moved from `openzeppelin_account::utils` to `openzeppelin_utils::execution`. If you're upgrading from v2.x, update these imports or compilation will fail. The `openzeppelin_interfaces` package versioning is now decoupled from the main umbrella package.

**Exact import paths (OZ Cairo 3.0.0):**
```cairo
use openzeppelin_security::InitializableComponent;
use openzeppelin_security::PausableComponent;
use openzeppelin_security::ReentrancyGuardComponent;
use openzeppelin_access::ownable::OwnableComponent;
use openzeppelin_access::accesscontrol::AccessControlComponent;
use openzeppelin_access::accesscontrol::default_admin_rules::AccessControlDefaultAdminRulesComponent;
use openzeppelin_upgrades::UpgradeableComponent;
use openzeppelin_utils::execution::{execute_single_call, execute_calls, assert_valid_signature};
```

### Initializable — One-Shot Constructor

For contracts where initialization must happen post-deploy (upgradeable patterns):

```cairo
use openzeppelin_security::InitializableComponent;

component!(path: InitializableComponent, storage: initializable, event: InitializableEvent);
impl InternalImpl = InitializableComponent::InternalImpl<ContractState>;

fn initializer(ref self: ContractState, owner: ContractAddress) {
    self.initializable.initialize(); // Panics on second call
    self.ownable.initializer(owner);
}
```

**Rule:** Only use `initialize()` in ONE function. If multiple init steps are needed, put them all in one initializer.

### Pausable — Emergency Stop

```cairo
use openzeppelin_security::PausableComponent;
use openzeppelin_access::ownable::OwnableComponent;

// Embed both components
#[abi(embed_v0)]
impl PausableImpl = PausableComponent::PausableImpl<ContractState>;
impl PausableInternalImpl = PausableComponent::InternalImpl<ContractState>;

#[external(v0)]
fn pause(ref self: ContractState) {
    self.ownable.assert_only_owner();
    self.pausable.pause();   // Emits Paused(account)
}

#[external(v0)]
fn unpause(ref self: ContractState) {
    self.ownable.assert_only_owner();
    self.pausable.unpause(); // Emits Unpaused(account)
}

// In protected functions:
fn transfer(ref self: ContractState, to: ContractAddress, amount: u256) {
    self.pausable.assert_not_paused(); // Blocks when paused
    // ... transfer logic
}
```

### ReentrancyGuard — Cross-Function Protection

Unlike Solidity modifiers, Cairo uses explicit `start()`/`end()` calls:

```cairo
use openzeppelin_security::ReentrancyGuardComponent;

component!(path: ReentrancyGuardComponent, storage: reentrancy_guard, event: ReentrancyGuardEvent);
impl InternalImpl = ReentrancyGuardComponent::InternalImpl<ContractState>;

#[external(v0)]
fn withdraw(ref self: ContractState, amount: u256) {
    self.reentrancy_guard.start();  // Panics if already entered

    let caller = get_caller_address();
    let balance = self.balances.read(caller);
    assert(balance >= amount, 'Insufficient');
    self.balances.write(caller, balance - amount);
    IERC20Dispatcher { contract_address: self.token.read() }.transfer(caller, amount);

    self.reentrancy_guard.end();    // Reset guard
}
```

**Rule:** `start()` must be the first statement, `end()` must be before `return`. The guard protects across ALL functions that use it — if `withdraw` is entered, `swap` (also guarded) cannot be called by the same tx.

### Pausable — Critical Note on Liquidations

Do NOT apply `assert_not_paused()` to liquidation or risk-management functions (see Section 11 "Pause Mechanism — Don't Pause Liquidations"). Emergency pause must still allow insolvent positions to be closed.

### OZ Governance Components

OZ Cairo 3.x includes a full governance suite. Key security patterns:

- **`GovernorComponent`** — on-chain voting with timelock executor. The executor address must be carefully controlled (set to the Timelock, not an EOA).
- **`TimelockControllerComponent`** — enforces delay between proposal and execution. PROPOSER / CANCELLER / EXECUTOR roles must be granted carefully. Set a meaningful minimum delay (gives users time to exit before governance changes take effect).
- **`MultisigComponent`** — multi-signature operations. Quorum must be set carefully (too low = insecure, too high = governance deadlock). OZ fixed a quorum-related bug in v0.18.0.
- **`VotesComponent`** — ERC20/ERC721 token voting with delegation and checkpoints.

**Governance security rules:**
1. `DEFAULT_ADMIN_ROLE` should be **renounced** after initial role setup (otherwise the admin can bypass governance).
2. Timelock minimum delay should be non-trivial (24-48h minimum) to give users time to react.
3. Governor executor must be the Timelock contract, NOT an arbitrary address.
4. For upgradeable contracts, the upgrade function should be behind the Timelock, not a single owner.
5. `GovernorComponent` proposal state at snapshot timepoint changed from Active to **Pending** in v3.0.0 — verify your governance UIs match this.
6. `VotesComponent` now supports customizable clock mechanisms via `ERC6372Clock` — ensure your voting token implements the correct clock source.

### AccessControlDefaultAdminRulesComponent (OZ v3.0.0)

*Source: [OZ v3.0.0 Release](https://github.com/OpenZeppelin/cairo-contracts/releases/tag/v3.0.0)*

New in v3.0.0. Enforces a **transfer delay** on `DEFAULT_ADMIN_ROLE`, preventing instant admin transfers that could be exploited in governance attacks. This is the recommended way to handle admin roles in production.

```cairo
use openzeppelin_access::accesscontrol::default_admin_rules::AccessControlDefaultAdminRulesComponent;

// Key features:
// - Admin transfer requires a two-step process with a configurable delay
// - MAXIMUM_DEFAULT_ADMIN_TRANSFER_DELAY exposed in ImmutableConfig
// - Prevents social engineering attacks where admin is transferred in a single tx
```

**Rule:** For any contract with `AccessControlComponent`, prefer `AccessControlDefaultAdminRulesComponent` for the admin role to enforce transfer delays.

### MetaTransactionV0 Preset (OZ v3.0.0)

New in v3.0.0. Provides a meta-transaction preset with built-in replay protection. Relevant for relayer architectures and paymaster integrations. Uses SNIP-12 for signature validation. If you're building a meta-transaction relay, use this instead of rolling your own.

---

## 18. DeFi Protocol Security Patterns

*Source: [Code4rena Starknet Perpetual (2025)](https://code4rena.com/reports/2025-03-starknet-perpetual)*

These patterns are specific to DeFi protocols (DEXes, lending, perpetuals, vaults) and emerge from the largest Cairo-specific competitive audit to date.

### Global Validation DoS (C4 L-06, L-08)

**Pattern:** A global validation function that checks ALL state (all asset prices, all funding rates) blocks operations that only involve a subset of state. If one unrelated asset has stale data, ALL operations fail — including unrelated withdrawals.

```cairo
// BAD — global validation blocks unrelated operations
fn reduce_position(ref self: ContractState, asset_id: felt252) {
    self._validate_all_assets_integrity(); // Checks ALL assets, fails if ANY is stale
    // User can't reduce their position because an unrelated asset has stale data
}

// GOOD — scope validation to affected assets only
fn reduce_position(ref self: ContractState, asset_id: felt252) {
    self._validate_asset_integrity(asset_id); // Only checks the relevant asset
    // User can proceed even if unrelated assets are stale
}
```

**Also from L-08:** Users with zero synthetic exposure were blocked from withdrawing collateral because validation ran unconditionally. **Rule:** Scope validation to the user's actual exposure — don't gate collateral-only operations on synthetic asset health.

### Stale Prices for Inactive Assets (C4 L-07)

When deactivating assets, ensure settlement/wind-down functions either: (a) allow governance to update inactive prices, or (b) validate price freshness explicitly. In the C4 finding, inactive assets couldn't have prices updated (the setter rejected them), but their last price was still used for settlement calculations with no freshness check.

### Liquidation Must Not Flip Position Direction (C4 M-02)

A liquidator can purchase more synthetic than the liquidated user holds, forcing them from long to short (or vice versa) without consent. **Rule:** Cap liquidation amounts at the existing synthetic balance. Use the same `_validate_imposed_reduction_trade()` pattern as deleverage to prevent direction flips.

### Per-Asset Parameterization (C4 L-01, L-02)

Using a single global `max_price_interval` or `max_funding_rate` for all synthetic assets is a design anti-pattern. Different asset classes have different volatility profiles. BTC and a long-tail memecoin should not share the same staleness threshold.

**Rule:** All risk parameters (price staleness windows, funding rate caps, collateral factors, liquidation thresholds) must be configurable per asset.

---

## 19. Security Tooling

*Source: [Caracal](https://github.com/crytic/caracal), [FuzzingLabs](https://github.com/FuzzingLabs), [Cairo Book ch104-03](https://book.cairo-lang.org/ch104-03-static-analysis-tools.html)*

### Primary Tool: snforge Fuzz Testing (Starknet Foundry)

**This is the only actively maintained, Cairo-2.12+-compatible security testing tool.** Use `snforge test` with fuzz testing as your primary automated security tool.

```bash
# Run all tests with fuzzing (default 256 runs)
snforge test

# Increase fuzz iterations for security-sensitive functions
snforge test --fuzzer-runs 1000

# Run specific test
snforge test test_deposit_withdraw_invariant
```

Write property-based fuzz tests for all arithmetic and state-transition logic:

```cairo
#[test]
#[fuzzer(runs: 500, seed: 42)]
fn fuzz_transfer_preserves_total_supply(amount: u128) {
    // Setup
    let initial_supply = token.total_supply();
    // Act
    token.transfer(recipient, amount.into());
    // Assert: total supply never changes
    assert(token.total_supply() == initial_supply, 'SUPPLY_CHANGED');
}
```

### Caracal — Static Analyzer (Trail of Bits / Crytic)

> **WARNING: Caracal v0.2.3 (released Jan 2024) only supports Cairo up to 2.5.0 and is effectively unusable for any project on Cairo 2.7+, which includes essentially every Starknet project since mid-2024.** Check the [releases page](https://github.com/crytic/caracal/releases) for updates. Until a new release ships, use `snforge` fuzz testing as your primary automated security tool.

If you are on Cairo ≤ 2.5.0 (legacy projects):

```bash
cargo install caracal
cd my_project && caracal .
caracal . --detectors reentrancy,unchecked_return
```

### FuzzingLabs Tools (Archived / No Longer Maintained)

> **WARNING:** All three FuzzingLabs tools — `cairo-fuzzer`, `sierra-analyzer`, and `Thoth` — are explicitly marked **"This repository is no longer maintained"** by FuzzingLabs. Additionally, `cairo-fuzzer` does not support Cairo 2.0+ contracts. **Do not rely on any of these for current projects.**

These tools made important contributions to the Cairo security ecosystem and their research remains valuable for understanding vulnerability classes, but they should not be part of your active toolchain:

- **sierra-analyzer** — Sierra decompiler with felt252 overflow detectors. [Archived](https://github.com/FuzzingLabs/sierra-analyzer)
- **cairo-fuzzer** — Smart contract fuzzer (Cairo 0.x only). [Archived](https://github.com/FuzzingLabs/cairo-fuzzer)
- **Thoth** — Bytecode disassembler, decompiler, symbolic execution. [Archived](https://github.com/FuzzingLabs/thoth)

### Recommended CI Pipeline

```yaml
# In .github/workflows/security.yml
- name: Build
  run: scarb build
- name: Test (with fuzzing)
  run: snforge test --fuzzer-runs 500
# Note: no static analyzer is currently compatible with Cairo 2.12+
# Monitor Caracal releases for updates
```

---

## 20. Upgrade Safety

### Before Upgrading

1. New class hash should be declared and verified on explorer
2. Test upgrade on Sepolia first
3. Verify storage layout compatibility
4. Have a rollback plan (old class hash declared, ready to re-upgrade)

### Storage Layout Rules

- Never remove or reorder existing storage fields
- Only append new fields at the end
- Component substorage names must stay the same
- Map key types must not change

---

## 21. Audit Preparation

### What Auditors Look For

1. **Access control completeness** — every external `ref self` function has authorization
2. **Input validation** — all user inputs checked before use
3. **State consistency** — no paths where state becomes inconsistent
4. **Economic invariants** — total supply == sum of balances, etc.
5. **Upgrade governance** — who can upgrade, timelocks
6. **Event completeness** — all state changes emit events
7. **Error messages** — all asserts have descriptive messages
8. **L1/L2 message safety** — from_address validated, replay protected
9. **Unbounded iteration** — no user-growable loops
10. **Boolean return checks** — ERC20 transfer/approve returns checked

### Documentation for Auditors

Provide:
- Architecture diagram (contracts + interactions)
- Invariants the system should maintain
- Known trust assumptions
- Admin capabilities and their risks
- Expected call flows for each user type
- L1/L2 message flow diagrams (if applicable)

---

## 22. Production Operations

### Monitoring

- Watch for unexpected `upgrade` calls
- Monitor admin role grants/revocations
- Track session key creation and revocation patterns
- Alert on large transfers or unusual call patterns
- Monitor L1/L2 message consumption (stuck messages)

### Incident Response

1. **Kill switch** — ability to pause the contract
2. **Session revocation** — revoke all active sessions immediately
3. **Upgrade path** — deploy fix, declare, upgrade
4. **Communication** — notify users via events and off-chain channels

---

## Sources

### Official Documentation
- [Cairo Book — General Recommendations (ch104)](https://book.cairo-lang.org/ch104-01-general-recommendations.html)
- [Cairo Book — Static Analysis Tools (ch104-03)](https://book.cairo-lang.org/ch104-03-static-analysis-tools.html)
- [OpenZeppelin Cairo Security Docs (3.x)](https://docs.openzeppelin.com/contracts-cairo/3.x/security)
- [OpenZeppelin Cairo Contracts Advisories](https://advisories.gitlab.com/pkg/pypi/openzeppelin-cairo-contracts)

### Audit Reports
- [Code4rena — Starknet Perpetual (2025), 2H/3M/14L](https://code4rena.com/reports/2025-03-starknet-perpetual)
- [Code4rena — Opus (Jan 2024), 4H/9M — first major Cairo DeFi audit](https://code4rena.com/reports/2024-01-opus)
- [ChainSecurity — Starknet Perpetual (2025)](https://www.chainsecurity.com/security-audit/starkware-starknet-perpetual)
- [ChainSecurity — MakerDAO StarkNet-DAI-Bridge (2021), 1 Critical](https://chainsecurity.com/wp-content/uploads/2021/12/ChainSecurity_MakerDAO_StarkNet-DAI-Bridge_audit.pdf)
- [ConsenSys Diligence — Argent Account V3 (Jan 2024)](https://diligence.consensys.io/audits/2024/01/argent-account-argent-multisig-starknet-transaction-v3-updates/)
- [Nethermind — 25+ Cairo/Starknet Audit Reports](https://github.com/NethermindEth/PublicAuditReports)
- [chipi-pay — Session Key Contract + SNIP Draft + 4 Nethermind AuditAgent scans](https://github.com/chipi-pay/sessions-smart-contract)
- [Code4rena — LayerZero Starknet Endpoint (Oct 2025), 0H/0M/6L](https://code4rena.com/reports/2025-10-layerzero-starknet-endpoint)
- [Cairo Security Clan — 30+ Cairo Audit Reports (Ekubo, Vesu, Opus, Paradex, etc.)](https://github.com/Cairo-Security-Clan/Audit-Portfolio)
- [ChainSecurity — Vesu Protocol Smart Contracts](https://www.chainsecurity.com/security-audit/vesu-protocol-smart-contracts)

### Exploit Post-Mortems
- [BlockSec — zkLend $10M Exploit Post-Mortem (Feb 2025)](https://blocksec.com/blog/zklend-exploit-post-mortem)
- [SolidityScan — zkLend Hack Analysis](https://blog.solidityscan.com/zklend-hack-analysis)
- [FuzzingLabs — Rediscovery of the zkLend Hack](https://fuzzinglabs.com/rediscovery-zklend-hack/)

### Vulnerability Research
- [Crytic — Not So Smart Contracts (Cairo)](https://github.com/crytic/building-secure-contracts/tree/master/not-so-smart-contracts/cairo)
- [0xEniotna — Starknet Contract Vulnerabilities](https://github.com/0xEniotna/Starknet-contracts-vulnerabilities)
- [Oxor.io — Cairo Security Flaws (Aug 2024)](https://oxor.io/blog/2024-08-16-cairo-security-flaws/)
- [Oxor.io — Overflow and Underflow Vulnerabilities in Cairo](https://oxor.io/blog/2024-08-16-overflow-and-underflow-vulnerabilities-in-cairo/)
- [FuzzingLabs — Top 4 Vulnerabilities in Cairo/Starknet (Nov 2024)](https://fuzzinglabs.com/top-4-vulnerability-cairo-starknet-smart-contract/)
- [amanusk — Awesome Starknet Security](https://github.com/amanusk/awesome-starknet-security)

### Standards & Specifications
- [SNIP-9 — Outside Execution (meta-transactions)](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-9.md)
- [SNIP-12 — Typed Structured Data Signing](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md)
- [OZ SNIP-12 Guide](https://docs.openzeppelin.com/contracts-cairo/3.x/guides/snip12)

### OpenZeppelin Components
- [OZ Cairo Governance Docs (3.x)](https://docs.openzeppelin.com/contracts-cairo/3.x/api/governance)
- [OZ Cairo ERC20Permit](https://docs.openzeppelin.com/contracts-cairo/3.x/api/erc20#ERC20Permit)
- [OZ Cairo NoncesComponent](https://docs.openzeppelin.com/contracts-cairo/3.x/api/utilities#NoncesComponent)

### Account Abstraction & Paymasters
- [Starknet Docs — Account Abstraction](https://docs.starknet.io/build/starknet-by-example/advanced/account-abstraction)
- [Starknet Docs — Paymaster](https://docs.starknet.io/build/applications/paymaster)

### Protocol Security Disclosures
- [Zellic — Sequencer-Prover Inconsistency in Cairo VM (Starknet Community Forum)](https://community.starknet.io/t/remediating-a-potential-sequencer-prover-inconsistency-in-the-cairo-vm/115313)

### Formal Verification
- [StarkWare — Cairo VM Formal Proofs (Lean)](https://github.com/starkware-libs/formal-proofs) — formal verification of Cairo VM semantics, AIR encoding correctness, and elliptic curve operations (secp256k1/r1)

### Protocol Changes
- [Starknet Version Notes (official, all versions)](https://docs.starknet.io/learn/cheatsheets/version-notes)
- [Starknet v0.14.0 "Grinta" — Decentralized Sequencer](https://starknet.io/blog/starknet-grinta-the-architecture-of-a-more-decentralized-future)
- [Starknet Version Releases](https://www.starknet.io/developers/version-releases/)
- [Starknet Fees Documentation](https://docs.starknet.io/learn/protocol/fees)
- [Starknet Compatibility Tables](https://docs.starknet.io/learn/cheatsheets/compatibility)
- [Cairo v2.15.0 Release (edition 2025_12)](https://github.com/starkware-libs/cairo/releases/tag/v2.15.0)

### Cairo Core Library
- [Cairo Core Integer Module (overflow/wrapping/saturating)](https://docs.cairo-lang.org/core/core-integer.html)
- [Cairo SaturatingAdd Trait](https://docs.cairo-lang.org/core/core-num-traits-ops-saturating-SaturatingAdd.html)

### Security Tooling
- [snforge — Starknet Foundry Testing & Fuzzing (primary tool)](https://foundry-rs.github.io/starknet-foundry/)
- [Caracal — Static Analyzer (Cairo ≤ 2.5.0 only, last release Jan 2024)](https://github.com/crytic/caracal)
- [FuzzingLabs — sierra-analyzer (ARCHIVED, no longer maintained)](https://github.com/FuzzingLabs/sierra-analyzer)
- [FuzzingLabs — cairo-fuzzer (ARCHIVED, no longer maintained, Cairo 0.x only)](https://github.com/FuzzingLabs/cairo-fuzzer)
- [FuzzingLabs — Thoth (ARCHIVED, no longer maintained)](https://github.com/FuzzingLabs/thoth)
- [sqrlfirst — Cairo Security Checklist](https://github.com/sqrlfirst/cairo-checklist)

## references/checklists

```

```

## references/checklists/release-gate.md

# Release Security Gate Checklist

- No unresolved Critical/High findings.
- Required regression tests added for all merged fixes.
- Static analysis and test suites pass.
- Findings disposition table updated.
- Held-out eval gate passes versus baseline scorecard (`evals/scorecards/v0.1.1-audit-pipeline.md`): no High/Critical recall regression and false-positive rate delta <= +1.0 percentage point.

## references/judging.md

# Finding Validation (Cairo)

Every finding must pass this gate before reporting.

## FP Gate (Required)

Drop the finding if any check fails.

1. **Concrete attack path** exists: caller -> reachable function -> state transition -> loss/impact.
2. **Reachability**: threat actor in scope can call the path under actual access control (`assert_only_*`, role checks, caller checks, account validation paths). If only `owner/admin/governance` can call it, keep it in scope as governance/admin risk and score accordingly.
3. **No existing guard** blocks the attack (`assert`, non-reentrant lock, OZ component guard, explicit invariant check).

## Confidence Score

Start at `100`, apply deductions:

- Privileged caller required (`owner/admin/governance`) -> `-25`
- Partial path (cannot prove full transition to impact) -> `-20`
- Impact self-contained to attacker-only funds -> `-15`
- Requires narrow environmental assumptions (sequencer timing / unusual off-chain behavior) -> `-10`
- Safety depends on indirect framework behavior that is present but not locally asserted -> `-10`

Report format uses `[score]` confidence tags.
Findings with confidence `<75` may be reported as low-confidence notes, without fix blocks.

## Do Not Report

- Style/naming/comments/NatSpec-only findings.
- Linter/compiler-only warnings already enforced by toolchain.
- Generic centralization notes without concrete exploit path.
- Privileged-only path reports without explicit governance/admin-risk framing.
- Gas-only micro-optimizations.
- Missing events when no concrete security or accounting impact exists.
- Pure documentation debt without exploitability.
- Theoretical attacks that require compromised prover/sequencer and no realistic trigger path.
- Duplicate root causes already captured by a higher-confidence finding.

## Cairo-Specific Notes

- Distinguish direct `replace_class_syscall` from OZ `UpgradeableComponent` paths.
- For constructor/address findings, separate critical role loss from expected deploy-time config.
- For session/account flows, reason across `__validate__` and `__execute__` jointly.

## references/report-formatting.md

# Report Formatting

## Report Path

When `--file-output` is set, save the report to `{repo-root}/security-review-{timestamp}.md` where `{timestamp}` is `YYYYMMDD-HHMMSS` at scan time (middle `MM` denotes minutes).

## Output Format

````markdown
# Security Review — <project name or repo basename>

---

## Scope

|                                  |                                                        |
| -------------------------------- | ------------------------------------------------------ |
| **Mode**                         | default / deep / targeted                              |
| **Files reviewed**               | `file1.cairo` · `file2.cairo`<br>`file3.cairo` · `file4.cairo` |
| **Total in-scope lines**         | N                                                      |
| **Confidence threshold (0-100)** | 75                                                     |
| **Preflight findings**           | N deterministic hits                                   |

---

## Findings

[P0] **1. <Title>**

`Class: CLASS_ID` · `file.cairo:line` · Confidence: 92 · Severity: Critical

**Description**
<One paragraph: exploit path and impact.>

**Fix**

```diff
- vulnerable line(s)
+ fixed line(s)
```

**Required Tests**
- Regression test that reproduces the vulnerable path.
- Guard test that proves fix blocks exploit.

---

[P1] **2. <Title>**

`Class: CLASS_ID` · `file.cairo:line` · Confidence: 85 · Severity: High

**Description**
<One paragraph: exploit path and impact.>

**Fix**

```diff
- vulnerable line(s)
+ fixed line(s)
```

**Required Tests**
- Regression test that reproduces the vulnerable path.
- Guard test that proves fix blocks exploit.

---

[P2] **3. <Title (below threshold example)>**

`Class: CLASS_ID` · `file.cairo:line` · Confidence: 68 · Severity: Medium

**Description**
<One paragraph: exploit path and impact.>

---

< ... remaining findings ... >

---

## Findings Index

| # | Priority | Confidence | Severity | Title |
|---|----------|------------|----------|-------|
| 1 | P0       | [92]       | Critical | <title> |
| 2 | P1       | [85]       | High     | <title> |
|   |          |            |          | **Below Confidence Threshold** |
| 3 | P2       | [68]       | Medium   | <title> |
| 4 | P3       | [55]       | Low      | <title> |

---

> This review was performed by an AI assistant. AI analysis cannot verify the complete absence of vulnerabilities and no guarantee of security is given. Team security reviews, formal audits, bug bounty programs, and on-chain monitoring are strongly recommended.

````

## Rules

- Follow the template above exactly.
- Sort findings by priority (`P0` first); within each priority tier, sort by confidence (highest first).
- Findings below threshold (confidence < 75) get a description but no **Fix** block and no **Required Tests** block.
- After filtering/deduplication/sorting, renumber findings sequentially starting at `1`.
- Do not re-draft or paraphrase finding content. Apply only the required structural transformations (FP-gate filtering, deduplication, sorting, threshold-based block removal, renumbering, and canonical section ordering), then emit the finding text verbatim.
- If any findings have confidence < 75, insert one **Below Confidence Threshold** separator row in the Findings Index immediately before the first below-threshold finding.
- Findings that fail FP gate must be dropped entirely and not reported.

## Finding Template (per finding)

Use this exact per-finding structure:

- `[P{priority}] **{index}. {title}**`
- `` `Class: {class_id}` · `{file}:{line}` · Confidence: {score} · Severity: {severity} ``
- `**Description**` then one paragraph with concrete exploit path and impact.
- `**Fix**` then a `diff` block (only for confidence >= 75).
- `**Required Tests**` then bullet list (only for confidence >= 75).

## Priority Mapping

- `P0`: direct loss, permanent lock, or upgrade takeover.
- `P1`: high-impact auth/logic flaw with realistic exploit path.
- `P2`: medium-impact misconfiguration or constrained exploit.
- `P3`: low-impact hardening issue.

## Deduplication Rule

When two findings share the same root cause, keep one:

- keep higher confidence,
- merge broader attack path details,
- keep a single fix/test block.

## references/semgrep

```

```

## references/semgrep/README.md

# Semgrep Adapter Rules (Cairo Auditor)

This directory contains optional Semgrep rules used as an auxiliary detector layer.

Design goals:
- fail-open in CI/local runs when Semgrep is unavailable,
- fast pattern coverage for high-signal classes,
- no replacement for deterministic detector and FP gate workflow.

Important:
- All generic Semgrep rules in this directory are low-confidence triage hints.
- CEI-oriented matches require manual ordering validation.
- Access-control mutation matches require manual guard-path validation.

Runner:
- `scripts/quality/run_semgrep_cairo.py`
- `scripts/quality/check_semgrep_vector_coverage.py`

Default config:
- `cairo-auditor/references/semgrep/rules/`

Rule packs:
- `rules/access-upgrade.yaml`
- `rules/external-calls.yaml`
- `rules/math-economic.yaml`
- `rules/storage-trust.yaml`

Coverage contract:
- `attack_vectors_core` metadata in Semgrep rules must cover core vectors `1..80`.
- CI enforces this via `scripts/quality/check_semgrep_vector_coverage.py`.

## references/semgrep/cairo-auditor-rules.yaml

```yaml

```

## references/semgrep/rules

```

```

## references/semgrep/rules/access-upgrade.yaml

```yaml

```

## references/semgrep/rules/external-calls.yaml

```yaml

```

## references/semgrep/rules/math-economic.yaml

```yaml

```

## references/semgrep/rules/storage-trust.yaml

```yaml

```

## references/vulnerability-db

```

```

## references/vulnerability-db/AA-SELF-CALL-SESSION.md

# AA-SELF-CALL-SESSION

## Class

Session key privilege escalation via account self-call.

## Description

Session-key execution paths must not allow calls back into the account contract for privileged selectors.
If self-calls are allowed, a compromised session key may invoke admin/state-changing selectors and escalate control.

## Detection Heuristic

Flag session-key `__execute__` paths where both are true:

- call target can equal `get_contract_address()`
- selector denylist does not block privileged account selectors

## Secure Pattern

- deny self-targeting calls for session-key paths (`call.to != self`)
- deny privileged selectors explicitly
- test both snake_case and camelCase selector variants when relevant

## False Positive Caveat

Owner-only administrative flows may permit self-calls; session-key flows must not.

## Required Tests

- session key cannot invoke account privileged selector
- owner path still functions correctly

## references/vulnerability-db/CEI-VIOLATION-ERC1155.md

# CEI-VIOLATION-ERC1155

## Description

Function performs ERC1155 `safe_transfer_from` (external interaction with callback surface)
before critical state updates, violating Check-Effects-Interactions ordering.

## Vulnerable Pattern

- interaction: ERC1155 safe transfer
- effects: order/claim/status writes happen after transfer
- no reentrancy guard around callback-capable path

## Secure Pattern

- apply effects before external interaction
- or enforce robust reentrancy guard around whole flow

## Detection Heuristics

- function contains `safe_transfer_from`
- state mutation markers occur after transfer call
- no visible reentrancy lock/guard pattern

## False Positive Caveats

- callback target is provably trusted and non-reentrant (rare; must be documented)
- reentrancy is blocked by upstream global lock

## Minimum Tests

- malicious callback contract cannot re-enter and double-process order
- state transition is committed before interaction or lock blocks recursion

## references/vulnerability-db/COMMENTED-OUT-ACCESS-CONTROL.md

# COMMENTED-OUT-ACCESS-CONTROL

## Description

Critical access checks are commented out while state-changing transfer paths remain active.

## Vulnerable Pattern

- commented guard calls (`only_controller`, role checks, auth asserts)
- public function continues into transfer/mutation sink

## Secure Pattern

- enforce live guard in executable path
- remove dead/testing bypass comments before release

## Detection Heuristics

- commented access-control marker in sensitive function
- no equivalent live guard after stripping comments

## False Positive Caveats

- comments in dead code path that is never reachable/exposed

## Minimum Tests

- unauthorized caller test must revert
- authorized caller happy-path test succeeds

## references/vulnerability-db/CONSTRUCTOR-DEAD-PARAM.md

# CONSTRUCTOR-DEAD-PARAM

## Description

Constructor accepts parameter(s) that are never used in initialization logic.
This creates misleading API surface and can hide misconfiguration or abandoned controls.

## Vulnerable Pattern

- constructor signature includes parameter
- parameter is not referenced in constructor body
- consumers assume parameter affects contract behavior

## Secure Pattern

- remove unused constructor parameters
- or consume them in explicit validated initialization flow

## Detection Heuristics

- parse constructor parameter list
- detect parameter identifiers with zero references in constructor body

## False Positive Caveats

- macro-generated code may consume parameters indirectly
- reference could exist only in conditional compile paths

## Minimum Tests

- constructor ABI matches actual required initialization fields
- deployment rejects stale/outdated constructor argument formats

## references/vulnerability-db/CRITICAL-ADDRESS-INIT-WITHOUT-NONZERO-GUARD.md

# CRITICAL-ADDRESS-INIT-WITHOUT-NONZERO-GUARD

## Description

Constructor stores privileged/critical `ContractAddress` parameters without non-zero validation.

## Vulnerable Pattern

- constructor receives addresses like `admin`, `owner`, `registry`, `vault`, `oracle`
- writes them to storage / role setup directly
- no `is_non_zero` assertion

## Secure Pattern

- validate each critical constructor address with non-zero checks before storing

## Detection Heuristics

- constructor includes critical `ContractAddress` params
- params used in `write(...)` / `initializer(...)` / `_grant_role(...)`
- missing nearby non-zero assertions

## False Positive Caveats

- some protocols intentionally allow zero sentinel values
- if intentional, must be explicitly documented and tested

## Minimum Tests

- constructor reverts when any critical address is zero
- constructor succeeds with valid addresses and persists expected state

## references/vulnerability-db/FEES-RECIPIENT-ZERO-DOS.md

# FEES-RECIPIENT-ZERO-DOS

## Description

Fee recipient is set without non-zero guard and later used in payout paths.
If set to zero address, fee distribution can revert and block core accounting flows.

## Vulnerable Pattern

- config setter writes `fees_recipient` directly
- no non-zero validation in setter path
- payout/report path depends on recipient for transfer/mint

## Secure Pattern

- enforce non-zero fee recipient in config setter
- optionally require recipient contract/interface checks when protocol requires

## Detection Heuristics

- `fees_recipient` written from parameter
- no non-zero check around assignment
- `fees_recipient` used in transfer/mint/report path

## False Positive Caveats

- protocol explicitly allows zero as burn sink and tests that behavior

## Minimum Tests

- setting zero recipient reverts
- report/payout succeeds for valid recipient
- changing recipient preserves report flow invariants

## references/vulnerability-db/IMMEDIATE-UPGRADE-WITHOUT-TIMELOCK.md

# IMMEDIATE-UPGRADE-WITHOUT-TIMELOCK

## Description

Contract supports direct class-hash upgrade in a single privileged call (`upgrade`/`replace_class`) without a delay window.

## Vulnerable Pattern

- `fn upgrade(...) { ... replace_class_syscall(...) }`
- no queued/scheduled upgrade state
- no minimum delay / timelock enforcement

## Secure Pattern

- split into `schedule_upgrade` + `execute_upgrade`
- store pending class hash + scheduled timestamp
- enforce `now >= scheduled_at + upgrade_delay`

## Detection Heuristics

- upgrade function performs `replace_class_syscall` or `upgradeable.upgrade`
- no timelock markers (`schedule_upgrade`, `upgrade_delay`, `pending_upgrade`, `timelock`)

## False Positive Caveats

- controlled environments may intentionally accept immediate upgrades
- external governance delays can exist off-chain (must be documented)

## Minimum Tests

- cannot execute upgrade before delay
- can execute only after delay expires
- cancel path clears pending upgrade state

## references/vulnerability-db/INCORRECT-LIST-REMOVAL.md

# INCORRECT-LIST-REMOVAL

## Description

Removal logic mutates the wrong list element, desynchronizing index mappings and stored state.

## Vulnerable Pattern

- function accepts target token/key but removes `pop_front()` unconditionally
- does not locate/remove target element by index

```cairo
fn remove_token(ref self: ContractState, token: felt252) {
    // BUG: ignores `token` and removes the front item.
    let removed = self.token_list.pop_front().expect('empty');
    // BUG: stale mapping for `token`; wrong mapping key removed.
    self.token_index_map.remove(removed);
}
```

## Secure Pattern

- locate exact index for target token/key and remove that position
- keep list and mapping updates atomic

```cairo
fn remove_token(ref self: ContractState, token: felt252) {
    let index = self.token_index_map.entry(token).read();
    assert!(index != 0_u32, 'token_not_found');

    let last_index = self.token_count.read() - 1_u32;
    let last_token = self.token_list.at(last_index).read();

    // Swap target with last and shrink explicit count (no pop_back API).
    if index != last_index {
        self.token_list.at(index).write(last_token);
        self.token_index_map.entry(last_token).write(index);
    }

    self.token_list.at(last_index).write(0);
    self.token_index_map.entry(token).write(0);
    self.token_count.write(last_index);
}
```

## Detection Heuristics

- targeted remove function with token arg and unconditional front-pop
- AST cue: function signature accepts a target identifier (for example `token`) but the list mutation path calls `pop_front()`/front removal without using that identifier for index resolution.
- Control-flow cue: no branch, loop, or helper call performs a target search between parameter intake and mutation.
- Data-flow cue: no taint/flow from target parameter reaches the computed removal index before list mutation.

## False Positive Caveats

- queue semantics intentionally remove oldest element regardless of token argument

## Minimum Tests

- remove-middle element preserves remaining order/mappings
- remove-missing target (non-existent token/key) either reverts or leaves list/mappings unchanged
- remove-target correctness under repeated operations
- remove-missing target on empty list reverts or is a no-op without mutating mappings

## references/vulnerability-db/IRREVOCABLE-ADMIN.md

# IRREVOCABLE-ADMIN

## Description

A privileged admin/owner is initialized but there is no explicit rotation, transfer,
or revocation path. Compromise or key loss can permanently block governance recovery.

## Vulnerable Pattern

- constructor seeds `admin`/`owner`/`upgrade_admin` storage
- privileged flows depend on that role
- no `set_*_admin`, `transfer_ownership`, `rotate_*`, or equivalent path

## Secure Pattern

- provide explicit role rotation/revocation lifecycle
- gate role mutation with strong access control and eventing
- test key-loss/compromise recovery runbooks

## Detection Heuristics

- constructor writes privileged address fields
- scan for absence of rotation primitives in ABI-exposed/admin methods

## False Positive Caveats

- immutable governance by design (must be explicitly documented and accepted)
- ownership lifecycle delegated to trusted component with verified hooks

## Minimum Tests

- old admin loses privileges after rotation
- new admin gains expected privileges
- unauthorized caller cannot rotate privileged role

## references/vulnerability-db/MISSING-FEE-BOUNDS.md

# MISSING-FEE-BOUNDS

## Description

Privileged runtime fee configuration accepts unbounded values, enabling DoS or abusive economics.

## Vulnerable Pattern

- setter writes `new_fee` directly
- setter is privileged/admin-facing runtime configuration
- no max bound/guard in setter path
- downstream logic assumes bounded fee range

## Secure Pattern

- enforce immutable upper bounds at setter edge
- validate per-asset fee updates against protocol constants

## Detection Heuristics

- privileged fee setter with direct storage write
- no assertion comparing new fee to max basis points/constant

## Distinguish From `UNCHECKED-FEE-BOUND`

- `MISSING-FEE-BOUNDS`: runtime configuration setter paths (owner/admin controlled) missing bounds.
- `UNCHECKED-FEE-BOUND`: externally supplied fee-like values forwarded to deploy/storage/external calls without validation.

## False Positive Caveats

- fee bounds enforced in shared component called by setter

## Minimum Tests

- out-of-range fee update reverts
- max-boundary fee update (`fee == MAX_FEE`) succeeds
- valid fee update preserves create/report flows

## references/vulnerability-db/NO-ACCESS-CONTROL-MUTATION.md

# NO-ACCESS-CONTROL-MUTATION

## Description

Privileged or configuration mutation functions are callable without explicit access control.
Unrestricted callers can alter protocol configuration or governance-critical state.

## Vulnerable Pattern

- external/public mutation function (`set_*`, `register_*`, `upgrade`, `pause`, etc.)
- writes privileged storage or role mappings
- no owner/role/caller check

## Secure Pattern

- gate privileged mutations with explicit owner/role checks
- centralize authorization paths and test denied callers

## Detection Heuristics

- risky mutation function names with storage writes/role mutation
- missing explicit access control markers in function path

## False Positive Caveats

- intentionally permissionless product features (must be documented)
- access control enforced by surrounding ABI/router layer

## Minimum Tests

- unauthorized caller reverts on mutation function
- authorized caller succeeds and state transitions are correct

## references/vulnerability-db/ONE-SHOT-REGISTRATION.md

# ONE-SHOT-REGISTRATION

## Description

Critical dependency registration is enforced as write-once without a safe operator
recovery/update path. A wrong first registration can permanently brick integrations.

## Vulnerable Pattern

- `register_*` function writes critical address with one-shot guard
- no `set_*`/`update_*`/recovery function for the same field
- downstream logic assumes registration is always valid

## Secure Pattern

- keep initial registration guard
- add owner/governance-controlled recovery setter with explicit constraints
- require event emission and operational runbook for updates

## Detection Heuristics

- detect one-shot guard around `self.<field>.read()`
- check writers for that field are limited to constructor + register function

## False Positive Caveats

- explicitly immutable architecture with documented migration strategy
- intentionally non-upgradeable minimal contracts with no external dependencies

## Minimum Tests

- first registration succeeds
- duplicate registration reverts
- authorized recovery/update path works when enabled
- unauthorized recovery/update attempts revert

## references/vulnerability-db/OVERLY-RESTRICTIVE-VALIDATION.md

# OVERLY-RESTRICTIVE-VALIDATION

## Description

Validation rejects safe/common user flows due to overly strict inequalities or constraints.

## Vulnerable Pattern

- uses strict `>` where `>=` is expected by product semantics
- blocks legitimate configurations (for example zero-cliff schedules)

## Secure Pattern

- encode constraints that match protocol/business rules exactly
- add compatibility tests for boundary values

## Detection Heuristics

- strict inequality on time/range boundaries in creation helpers
- user-impacting branch with no security rationale

## False Positive Caveats

- strict inequality is intentional and explicitly documented as product rule

## Minimum Tests

- boundary tests for equal-value configurations
- tests proving rejected configurations are unsafe if intentionally disallowed

## references/vulnerability-db/PRECISION-LOSS.md

# PRECISION-LOSS

## Description

Percentage and vesting math is performed with an insufficient scale factor, causing material truncation and accounting drift.

## Vulnerable Pattern

- helper uses low fixed scale (for example `SCALE_FACTOR = 100`)
- scaled division result is reused for value transfer or streaming calculations
- no invariant compensates for truncation over long durations

## Secure Pattern

- use higher precision fixed-point math for time/price percentages
- round in a protocol-safe direction and document policy
- include invariants on cumulative streamed/withdrawn values

## Detection Heuristics

- fixed small-scale constant used in division helpers
- helper output multiplied with token amounts and divided back by the same scale

## False Positive Caveats

- intentionally coarse precision with explicit product requirements and tests

## Minimum Tests

- long-duration stream precision regression test
- cumulative withdrawn + refunded + remaining consistency check

## references/vulnerability-db/README.md

# Vulnerability DB

Canonical, generalized Cairo/Starknet vulnerability patterns.

One file per class with:

- class id
- description
- vulnerable pattern
- secure pattern
- detection heuristics
- false-positive caveats
- minimum required tests

Current classes:

This table currently lists **28 vulnerability classes**.

| Doc Slug | Detector Class ID |
| --- | --- |
| `AA-SELF-CALL-SESSION` | `AA-SELF-CALL-SESSION` |
| `UNCHECKED-FEE-BOUND` | `UNCHECKED_FEE_BOUND` |
| `SHUTDOWN-OVERRIDE-PRECEDENCE` | `SHUTDOWN_OVERRIDE_PRECEDENCE` |
| `SYSCALL-SELECTOR-FALLBACK-ASSUMPTION` | `SYSCALL_SELECTOR_FALLBACK_ASSUMPTION` |
| `IMMEDIATE-UPGRADE-WITHOUT-TIMELOCK` | `IMMEDIATE_UPGRADE_WITHOUT_TIMELOCK` |
| `UPGRADE-CLASS-HASH-WITHOUT-NONZERO-GUARD` | `UPGRADE_CLASS_HASH_WITHOUT_NONZERO_GUARD` |
| `CRITICAL-ADDRESS-INIT-WITHOUT-NONZERO-GUARD` | `CRITICAL_ADDRESS_INIT_WITHOUT_NONZERO_GUARD` |
| `CONSTRUCTOR-DEAD-PARAM` | `CONSTRUCTOR_DEAD_PARAM` |
| `FEES-RECIPIENT-ZERO-DOS` | `FEES_RECIPIENT_ZERO_DOS` |
| `NO-ACCESS-CONTROL-MUTATION` | `NO_ACCESS_CONTROL_MUTATION` |
| `CEI-VIOLATION-ERC1155` | `CEI_VIOLATION_ERC1155` |
| `IRREVOCABLE-ADMIN` | `IRREVOCABLE_ADMIN` |
| `ONE-SHOT-REGISTRATION` | `ONE_SHOT_REGISTRATION` |
| `PRECISION-LOSS` | `PRECISION_LOSS` |
| `UNSAFE-ADMIN-TRANSFER` | `UNSAFE_ADMIN_TRANSFER` |
| `STALE-STATE-WRITE` | `STALE_STATE_WRITE` |
| `UNEXPECTED-ACCESS-CONTROL` | `UNEXPECTED_ACCESS_CONTROL` |
| `MISSING-FEE-BOUNDS` | `MISSING_FEE_BOUNDS` |
| `OVERLY-RESTRICTIVE-VALIDATION` | `OVERLY_RESTRICTIVE_VALIDATION` |
| `UNBOUNDED-LOOP` | `UNBOUNDED_LOOP` |
| `COMMENTED-OUT-ACCESS-CONTROL` | `COMMENTED_OUT_ACCESS_CONTROL` |
| `UNVALIDATED-ORACLE-PRICES` | `UNVALIDATED_ORACLE_PRICES` |
| `WRONG-PARAMETER-USAGE` | `WRONG_PARAMETER_USAGE` |
| `SILENT-NO-OP` | `SILENT_NO_OP` |
| `UNPROTECTED-INITIALIZER` | `UNPROTECTED_INITIALIZER` |
| `UNSAFE-TYPE-CONVERSION` | `UNSAFE_TYPE_CONVERSION` |
| `INCORRECT-LIST-REMOVAL` | `INCORRECT_LIST_REMOVAL` |
| `STALE-SNAPSHOT-READ` | `STALE_SNAPSHOT_READ` |

## references/vulnerability-db/SHUTDOWN-OVERRIDE-PRECEDENCE.md

# SHUTDOWN-OVERRIDE-PRECEDENCE

## Description

Contract computes both an inferred shutdown mode and an explicit fixed override.
If inferred mode is checked first with early return, admin override can be shadowed.

## Vulnerable Pattern

- inferred mode is evaluated first
- function returns inferred mode before reading fixed/admin override
- fixed override becomes ineffective during certain states

## Secure Pattern

- check explicit fixed/admin override first
- return inferred mode only when fixed override is not active

## Detection Heuristics

- function combines inferred mode + fixed override mode
- inferred path has early return before fixed override check
- both compare against same sentinel (`NONE`, `0`, etc.)

## False Positive Caveats

- no admin override exists by design
- inferred mode is intentionally authoritative and documented

## Minimum Tests

- both inferred + fixed active returns fixed override
- fixed-only and inferred-only behaviors are independently tested

## references/vulnerability-db/SILENT-NO-OP.md

# SILENT-NO-OP

## Description

A branch intended to update state performs no action, silently skipping required updates.

## Vulnerable Pattern

- `match/if` branch returns unit `()` or empty branch on existing-key path
- expected update call is absent for non-empty branch

## Secure Pattern

- update existing entries explicitly
- emit event or return status when no-op is intentional

## Detection Heuristics

- branch matching `Option::Some` (or equivalent) with empty action in state update function

## False Positive Caveats

- explicit idempotent behavior with complete test coverage and documentation

## Minimum Tests

- existing-item update test changes state
- idempotence behavior test if intentionally no-op
- intentional no-op path emits explicit status/event signal

## references/vulnerability-db/STALE-SNAPSHOT-READ.md

# STALE-SNAPSHOT-READ

## Description

Function reads from an immutable snapshot while mutating live state in the same loop, creating stale read decisions.

## Vulnerable Pattern

- captures `let self_copy = @self` snapshot
- reads critical values through snapshot
- writes state with mutable `self` in same iteration

## Secure Pattern

- use consistent mutable/read context for dependent operations
- re-read live state after each mutation where ordering matters

## Detection Heuristics

- snapshot alias used for reads (`self_copy.*`) in loop with mutable writes (`self.*`)

## False Positive Caveats

- snapshot reads are intentionally independent from mutated fields

## Minimum Tests

- sequential update/read consistency test in same transaction
- invariant checks for state-dependent loop updates

## references/vulnerability-db/STALE-STATE-WRITE.md

# STALE-STATE-WRITE

## Description

A state field is updated and then overwritten later in the same flow using stale pre-update values.

## Vulnerable Pattern

- first write stores incremented/decremented value
- later conditional write reconstructs struct with old field value
- final state is inconsistent with transferred amounts

## Secure Pattern

- derive second write from latest state snapshot
- avoid duplicating struct reconstruction across branches

## Detection Heuristics

- same field written twice in a function
- second write references pre-update variable while branch condition uses post-update data

## False Positive Caveats

- explicit rollback/compensation logic where second write is intentional and tested

## Minimum Tests

- post-withdraw consistency invariant for aggregate balances
- branch test covering depletion/finalization path

## references/vulnerability-db/SYSCALL-SELECTOR-FALLBACK-ASSUMPTION.md

# SYSCALL-SELECTOR-FALLBACK-ASSUMPTION

## Description

Code retries `call_contract_syscall` with alternate selector casing when first call errors.
This assumes recoverable fallback behavior that can hide real compatibility bugs.

## Vulnerable Pattern

- `call_contract_syscall(..., SELECTOR_A, ...)`
- branch on `result.is_err()`
- retry with `SELECTOR_B` for same logical function

## Secure Pattern

- use canonical selector only
- fail fast on syscall errors with explicit error surface

## Detection Heuristics

- one syscall followed by `is_err` branch
- second syscall in error branch with different selector
- both selectors target same operation semantics

## False Positive Caveats

- offchain simulation-only helper code
- test-only compatibility adapters with explicit scope

## Minimum Tests

- failing syscall path reverts deterministically (no fallback retry)
- successful canonical selector path returns expected result

## references/vulnerability-db/UNBOUNDED-LOOP.md

# UNBOUNDED-LOOP

## Description

View/mutation functions iterate over unbounded state size and become unusable as data grows.

## Vulnerable Pattern

- loop upper bound tied to global counter/state length
- no pagination/limit parameter
- linear scan over full dataset for filtered queries

## Secure Pattern

- support pagination (`start`, `limit`) and bounded iteration
- expose index mappings for targeted retrieval

## Detection Heuristics

- function loops from `1..next_id` or `0..len` and reads storage each iteration
- returns aggregate list without caller-supplied bound

## False Positive Caveats

- internal/admin-only maintenance function not exposed to user paths

## Minimum Tests

- pagination correctness tests
- large-state query gas/time budget tests

## references/vulnerability-db/UNCHECKED-FEE-BOUND.md

# UNCHECKED-FEE-BOUND

## Description

Caller-provided fee/rate parameter is forwarded to deployment or storage without range validation.
Out-of-bounds fee values can break economics, accounting, or protocol assumptions.

## Vulnerable Pattern

- fee/rate/bps parameter accepted from external caller
- value passed directly to `deploy_syscall` calldata or storage write
- missing max/min bound assertion

## Secure Pattern

- define protocol fee limits (`MAX_FEE_BPS`, optional `MIN_FEE_BPS`)
- validate fee bounds before writing or forwarding

## Detection Heuristics

- function accepts fee-like numeric parameter
- parameter is written/forwarded (`.write`, `deploy_syscall`, external call)
- no nearby bound check on that parameter

## False Positive Caveats

- value already constrained by typed enum-like wrapper
- bounds guaranteed by trusted upstream invariant

## Minimum Tests

- max allowed fee succeeds
- `max + 1` reverts with expected error
- zero-fee behavior is explicitly asserted per protocol policy

## references/vulnerability-db/UNEXPECTED-ACCESS-CONTROL.md

# UNEXPECTED-ACCESS-CONTROL

## Description

A privileged action is callable by an unexpected actor class, creating governance or economic behavior divergence.

## Vulnerable Pattern

- authorization check permits extra caller path (`sender || recipient`, etc.)
- operation mutates lifecycle-critical state (cancel, settle, upgrade, withdraw)

## Secure Pattern

- enforce actor set from protocol specification
- separate user actions from privileged lifecycle operations

## Detection Heuristics

- OR-combined authorization for sensitive function
- secondary actor path not aligned with documented role intent

## False Positive Caveats

- protocol intentionally supports multi-party cancellation and documents it clearly

## Minimum Tests

- actor matrix tests for sensitive functions
- regression test for spec-aligned authorization rules

## references/vulnerability-db/UNPROTECTED-INITIALIZER.md

# UNPROTECTED-INITIALIZER

## Description

Initializer function remains externally callable without caller authorization, enabling front-run or hostile initialization.

## Vulnerable Pattern

- public `initialize` outside constructor-only path
- only guard is `is_zero()` one-time check
- initializer writes privileged contract addresses/roles

## Secure Pattern

- constructor-only initialization, or
- protected initializer restricted to deployer/factory/governance

## Detection Heuristics

- publicly reachable `initialize` with storage writes
- no access control assertions in initializer body

## False Positive Caveats

- initializer callable only in factory-controlled deployment transaction pattern with proven atomicity

## Minimum Tests

- unauthorized initializer call reverts
- repeated initialization reverts

## references/vulnerability-db/UNSAFE-ADMIN-TRANSFER.md

# UNSAFE-ADMIN-TRANSFER

## Description

Admin transfer is single-step and unguarded, so a typo or malicious value can permanently lose privileged control.

## Vulnerable Pattern

- `transfer_admin(new_admin)` writes admin directly
- no non-zero validation for `new_admin`
- no two-step accept/claim flow

## Secure Pattern

- require non-zero `new_admin`
- use pending-admin + accept-admin two-step handover
- emit transition events for both schedule and accept

## Detection Heuristics

- direct admin storage write in `transfer_admin`
- absence of non-zero assertion and pending/accept surfaces

## False Positive Caveats

- deliberately immutable admin role in non-upgradeable/deprecated contracts with explicit governance controls

## Minimum Tests

- zero admin transfer reverts
- pending admin must accept before role activation
- schedule and accept transitions emit admin-handover events

## references/vulnerability-db/UNSAFE-TYPE-CONVERSION.md

# UNSAFE-TYPE-CONVERSION

## Description

Narrowing integer conversion can panic or truncate unexpectedly, causing DoS or logic corruption.

## Vulnerable Pattern

- `u256` converted via `try_into::<u32/u64>()` in critical path
- conversion failure leads to `expect()` panic

## Secure Pattern

- pre-validate bounds before narrowing
- use safe saturation/checked conversion policy with explicit error handling

## Detection Heuristics

- `try_into().expect('u256 into u32 failed')` (or equivalent) in runtime checks

## False Positive Caveats

- conversion source is statically bounded by invariant and proven in tests/formal checks

## Minimum Tests

- out-of-range conversion input reverts with controlled error
- in-range conversion path remains functional

## references/vulnerability-db/UNVALIDATED-ORACLE-PRICES.md

# UNVALIDATED-ORACLE-PRICES

## Description

Oracle price updates bypass signature/median/deviation validations and accept raw input values.

## Vulnerable Pattern

- validated path disabled/commented with TODO markers
- test-only fast path writes primary prices directly from compacted arrays

## Secure Pattern

- route all updates through validation pipeline
- fail closed when validation modules are disabled/unavailable

## Detection Heuristics

- `set_prices` uses direct `set_primary_price_` writes from compacted arrays
- commented `set_prices_`/`validate_prices_` path indicators

## False Positive Caveats

- dedicated test contract/module not deployed to production

## Minimum Tests

- invalid signer/price deviation cases revert
- median/signature path coverage tests
- validator-disabled/unavailable path must fail closed (price update reverts)

## references/vulnerability-db/UPGRADE-CLASS-HASH-WITHOUT-NONZERO-GUARD.md

# UPGRADE-CLASS-HASH-WITHOUT-NONZERO-GUARD

## Description

Upgrade path accepts `new_class_hash` without explicit non-zero validation.

## Vulnerable Pattern

- upgrade function forwards `new_class_hash` directly to class replacement
- no `is_non_zero` / equivalent assertion on `new_class_hash`

## Secure Pattern

- `assert(new_class_hash.is_non_zero(), '...')` before upgrade call

## Detection Heuristics

- function `upgrade(... new_class_hash ...)` present
- calls `replace_class_syscall` or `upgradeable.upgrade`
- missing non-zero guard around `new_class_hash`

## False Positive Caveats

- some upgrade libraries may reject zero hash internally
- explicit guard still recommended for clearer invariant and error surface

## Minimum Tests

- upgrade with zero class hash reverts
- upgrade with valid class hash succeeds under authorized caller

## references/vulnerability-db/WRONG-PARAMETER-USAGE.md

# WRONG-PARAMETER-USAGE

## Description

Code assigns semantically distinct fields from the same parameter source, corrupting pricing/risk semantics.

## Vulnerable Pattern

- constructs `{min, max}` (or similar pair) from the same max-only input
- ignores dedicated min/source parameter

## Secure Pattern

- map each field from the intended parameter set
- assert `min <= max` and enforce spread policy

## Detection Heuristics

- `min:` and `max:` both populated from `compacted_max_prices` (or equivalent)

## False Positive Caveats

- protocol intentionally uses fixed-price mode and documents `min == max`

## Minimum Tests

- min/max source mapping tests
- spread/invariant tests for manipulated input packs

## scripts

```

```

## scripts/README.md

# Scripts

Use scripts in this directory to transform raw audit text into normalized finding records.

## workflows

```

```

## workflows/deep.md

# Deep Workflow

Extends default with adversarial reasoning. Orchestrated by [SKILL.md](../SKILL.md).

## Pipeline

1. **Discover** — same as default.
2. **Prepare** — same as default, plus resolve adversarial agent instructions.
3. **Spawn** — 4 parallel vector specialists (`model: "sonnet"`) + 1 adversarial specialist (`model: "opus"`), all in parallel.
4. **Report** — Merge all 5 agent outputs, deduplicate, sort, emit.

## Agent Configuration

| Agent | Model | Input | Role |
|-------|-------|-------|------|
| 1–4 | sonnet | Bundle files | Vector scan (same as default) |
| 5 | opus | Direct file reads + adversarial.md | Free-form adversarial reasoning |

## Agent 5 — Adversarial Specialist

- No attack vector reference — reasons freely about logic errors, unsafe interactions, multi-step chains.
- Reads all in-scope files directly (not via bundle).
- Focuses on: cross-function boundary reasoning, trust-chain composition, session/account interplay, upgrade failure modes.
- Applies FP gate and confidence scoring per `judging.md`.
- Higher cost but catches findings that pattern-based scanning misses.

## When to Use Deep Mode

- Pre-deployment security review for high-value contracts.
- Contracts with complex account abstraction, session key, or multi-sig logic.
- When default mode findings suggest deeper issues worth investigating.
- Release-gate audits where thoroughness outweighs speed.

## workflows/default.md

# Default Workflow

Standard 4-agent parallel scan. Orchestrated by [SKILL.md](../SKILL.md).

## Pipeline

1. **Discover** — `find` in-scope `.cairo` files, run deterministic preflight.
2. **Prepare** — Read `vector-scan.md`, build 4 bundle files (code + judging + formatting + one attack-vector partition each).
3. **Spawn** — 4 parallel vector specialists (`model: "sonnet"`), each triages vectors, deep-checks survivors, applies FP gate.
4. **Report** — Merge, deduplicate by root cause, sort by confidence, emit with scope table and disclaimer.

## Agent Configuration

| Agent | Model | Input | Role |
|-------|-------|-------|------|
| 1 | sonnet | Bundle 1 (Access Control + Upgradeability) | Vector scan |
| 2 | sonnet | Bundle 2 (External Calls + Reentrancy) | Vector scan |
| 3 | sonnet | Bundle 3 (Math + Pricing + Economics) | Vector scan |
| 4 | sonnet | Bundle 4 (Storage + Components + Trust) | Vector scan |

## Confidence Threshold

- Findings >= 75: full report with fix diff and required tests.
- Findings < 75: low-confidence notes, no fix block.
- Findings failing FP gate: dropped entirely.

