Fizz Convert
pashov/skills
Convert English-language properties in PROPERTIES.md (produced by the Fizz skill) into Solidity assertions inside the existing fuzz harness, then flip their checkboxes.
Deep security audit of a smart-contract repo OR a live on-chain contract by address - detect Solidity, model the protocol invariants and trust boundaries first, run Slither (best-effort) plus a…
$ npx skills add aeonfun/aeon --skill sc-audit -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install aeonfun/aeon sc-audit --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/aeonfun/aeon.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/sc-audit .claude/skills/sc-audit && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "sc-audit" agent skill from https://github.com/aeonfun/aeon/tree/main/skills/sc-audit into .claude/skills/sc-audit/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "sc-audit", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/aeonfun/aeon/tree/main/skills/sc-auditType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add aeonfun/aeon --skill sc-audit -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install aeonfun/aeon sc-audit --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/aeonfun/aeon.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/sc-audit .agents/skills/sc-audit && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "sc-audit" agent skill from https://github.com/aeonfun/aeon/tree/main/skills/sc-audit into .agents/skills/sc-audit/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "sc-audit", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add aeonfun/aeon --skill sc-audit -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install aeonfun/aeon sc-audit --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/aeonfun/aeon.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/sc-audit .cursor/skills/sc-audit && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "sc-audit" agent skill from https://github.com/aeonfun/aeon/tree/main/skills/sc-audit into .cursor/skills/sc-audit/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "sc-audit", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/aeonfun/aeon.git --path skills/sc-audit--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add aeonfun/aeon --skill sc-audit -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install aeonfun/aeon sc-audit --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/aeonfun/aeon.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/sc-audit .gemini/skills/sc-audit && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "sc-audit" agent skill from https://github.com/aeonfun/aeon/tree/main/skills/sc-audit into .gemini/skills/sc-audit/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "sc-audit", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install aeonfun/aeon sc-auditInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add aeonfun/aeon --skill sc-audit -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/aeonfun/aeon.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/sc-audit .github/skills/sc-audit && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "sc-audit" agent skill from https://github.com/aeonfun/aeon/tree/main/skills/sc-audit into .github/skills/sc-audit/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "sc-audit", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add aeonfun/aeon --skill sc-audit -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install aeonfun/aeon sc-audit --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/aeonfun/aeon.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/sc-audit .opencode/skills/sc-audit && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "sc-audit" agent skill from https://github.com/aeonfun/aeon/tree/main/skills/sc-audit into .opencode/skills/sc-audit/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "sc-audit", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
sc-auditDeep security audit of a smart-contract repo OR a live on-chain contract by address - detect Solidity, model the protocol invariants and trust boundaries first, run Slither (best-effort) plus a…
Sc Audit is an agent skill from aeonfun/aeon. Deep security audit of a smart-contract repo OR a live on-chain contract by address - detect Solidity, model the protocol invariants and trust boundaries first, run Slither (best-effort) plus a bounded agentic pass that hunts for a path breaking each invariant, triage, adversarially verify, prove with a fuzzer, and drive each finding through the shared responsible-disclosure routing. The dedicated contract arm split out of vuln-scanner.
Its SKILL.md is about 18k tokens, which your agent loads only when the skill is triggered. The skill folder holds 8 other files, including reference files (for example `fixtures/README.md` and `references/hook-checklist.md`).
It sits in Backend & APIs, covering Smart contracts. It works with Solidity. The repository describes itself as: The most autonomous AI agent framework: runs unattended on GitHub Actions, self-healing skills, drives Claude Code, Grok, Codex & more. No approval loops. Configure once, forget… The licence is MIT.
3 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit f252074. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
python3curlghgitclaudenpxnpmFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
github.comapi.etherscan.ioregistry.npmjs.orgsourcify.devbasescan.orgFrom URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
ETHERSCAN_API_KEYBLOCKSCOUT_API_KEYFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Sc Audit loads about 18k tokens when it runs, and up to ~20k if it reads all its reference files. Until then it costs about 112 tokens; SKILL.md has 6,338 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from aeonfun/aeon at commit f252074, republished under its MIT licence (© aeonfun). 6,338 words, ~17,537 tokens.
.claude/skills/sc-audit/SKILL.md (or your agent's skills folder). This skill also uses 4 other files; get the full folder from GitHub.${var} - Target selector. Four forms:
- `` (empty) -> auto-select the day's fresh feed target, audit only if it contains Solidity, else exit clean. See §S1 for the selection order (an optional
sc-targets.jsonledger, else thegithub-trendingfeed).owner/repo-> audit that GitHub repo (e.g.Uniswap/v4-core,aave/aave-v3-origin).<chain>:0x<address>-> on-chain mode: audit a live deployed contract by address. It fetches the verified source from the block explorer (Etherscan V2) or Sourcify, materializes it as a Foundry project, and runs the same pipeline - plus on-chain context (proxy/implementation, owner/admin, funds at risk).chainineth/base/arbitrum/optimism/polygon/bsc/... (bare0x<address>defaults toeth). Examples:base:0x4200000000000000000000000000000000000006,eth:0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2. On-chain findings are operator-gated - never auto-filed/auto-emailed (see §S7). See §S1/§S2.fixture:<name>-> local regression mode: audit the bundled fixture atskills/sc-audit/fixtures/<name>/(e.g.fixture:vault). No fork, no dedup, no disclosure - a self-contained way to exercise the whole pipeline including the fuzz arm, with no external repo. See §S1/§S2.
Today is ${today}. Read memory/MEMORY.md and the last 30 days of memory/logs/ before starting.
Smart-contract bugs are logic and economics, not syntax. Slither matches known static patterns; it is weak on access control, protocol invariants, oracle/price manipulation, rounding/precision, upgradeability storage collisions, and cross-contract reentrancy - the classes that actually drain funds, and where exploitation on-chain is immediate and irreversible. That whole class is what an agentic reviewer catches by reading the source and reasoning about who can call what and which invariant breaks.
This skill is the contract arm split out of vuln-scanner. vuln-scanner detects Solidity and hands the repo here rather than running Slither inline; this skill owns the deep audit and then routes findings through vuln-scanner's shared disclosure machinery. It does not duplicate the disclosure/PVR/email logic - see §S7.
It audits two kinds of target: a GitHub repo (source in a repo you fork), and a live contract deployed on-chain by address (<chain>:0x<addr>) - the latter fetches the verified source from the block explorer/Sourcify and adds on-chain context (proxy/implementation, owner, funds at risk). A live deployed contract is where a bug is already exploitable with real money at stake, so on-chain findings are treated as the highest-stakes disclosure and are operator-gated - staged for a human, never auto-filed (§S7).
The agentic source pass is the reliable core. Slither is best-effort: a headless run has slither allow-listed but not solc / forge / solc-select, so contracts that need a compiler to build may not compile in-run. The source pass needs no compiler, so a clean audit never depends on Slither succeeding.
REPO="${var}" # owner/repo | empty (auto) | fixture:<name> | <chain>:0x<addr> | 0x<addr>
if [ "${REPO#fixture:}" != "$REPO" ]; then MODE=fixture; FIXTURE="${REPO#fixture:}"
elif [[ "$REPO" =~ ^([a-zA-Z0-9-]+:)?0x[0-9a-fA-F]{40}$ ]]; then MODE=onchain # deployed-contract selector
else MODE=repo; fi # owner/repo, or empty -> auto-selectMODE=fixture -> audit the bundled fixture skills/sc-audit/fixtures/$FIXTURE/ (see §S2). Skip the dedup ledger and skip disclosure entirely (§S7/§S8) - fixtures are deliberately-vulnerable regression targets meant to be re-run on demand, never disclosed. If the fixture dir is missing, log no-fixture: $FIXTURE and exit clean.
MODE=repo, $REPO set -> that is the target. Confirm it holds Solidity before forking: gh api /search/code?q=repo:$REPO+extension:sol --jq '.total_count' (or just proceed and detect after clone in S2). If the repo has no *.sol, log no-solidity: $REPO and exit clean - this is the wrong skill for it.
MODE=repo, $REPO empty -> auto-select a repo target, best-first:
sc-source-style ledger exists at memory/sc-targets.json (schema {updated, repos:[{repo, stars, tier, desc, first_seen}]}, best-first, already Solidity-scoped and deduped), read it and take repos[0].repo. If that repo was scanned within 30 days per memory/vuln-scanned.json, or turns out to hold no real Solidity after clone (S2), walk down repos[]. This ledger is optional; the skill stands alone without it.github-trending feed at output/.chains/github-trending.md (most recent by ISO header date) and walk its repos for one that contains Solidity.no-solidity-target and exit clean.On-chain candidates never enter auto-mode - the empty-$REPO path is repo-only. Audit a live contract by passing an explicit <chain>:0x<addr> selector.
MODE=onchain -> resolve the chain to an Etherscan V2 chain id and normalize the address, then fetch verified source in §S2:
ADDR="${REPO##*:}" # after last ':' (or the whole string if none)
CHAIN="${REPO%:*}"; [ "$CHAIN" = "$REPO" ] && CHAIN="eth" # before ':' or default eth
CHAIN=$(printf '%s' "$CHAIN" | tr 'A-Z' 'a-z')
ADDR=$(printf '%s' "$ADDR" | tr 'A-Z' 'a-z') # lowercase; explorer/Sourcify are checksum-insensitive
case "$CHAIN" in
eth|ethereum|mainnet) CID=1 ;; base) CID=8453 ;;
arbitrum|arb) CID=42161 ;; optimism|op) CID=10 ;;
polygon|matic) CID=137 ;; bsc|bnb) CID=56 ;;
avalanche|avax) CID=43114 ;; gnosis|xdai) CID=100 ;;
scroll) CID=534352 ;; linea) CID=59144 ;;
zksync) CID=324 ;; blast) CID=81457 ;;
sepolia) CID=11155111 ;; base-sepolia) CID=84532 ;;
*) echo "unknown-chain: $CHAIN (add its Etherscan V2 chainid to S1 to support it)"; exit 0 ;;
esac
echo "onchain target: chain=$CHAIN cid=$CID addr=$ADDR"For a Blockscout-only chain not on Etherscan V2, add its BLOCKSCOUT host in §S2b and the Etherscan calls no-op cleanly (verified source comes from the Sourcify/Blockscout fallback).
On-chain findings are operator-gated - a live contract holding funds is the highest-stakes disclosure, so this mode NEVER auto-files a PVR, auto-sends an email, or opens any public channel; it stages an operator-gated draft and notifies (see §S7).
Dedup (mandatory in MODE=repo and MODE=onchain, same ledger as vuln-scanner). Before auditing, skip a target already covered in the last 30 days: read memory/vuln-scanned.json and skip any row inside the window - keyed on $REPO for a repo, on onchain:$CHAIN:$ADDR for an address. For a repo, also check gh api /repos/$REPO/security-advisories - a repo with a published/credited advisory for the same finding class is already handled; skip and log. This is the identical dedup contract described in vuln-scanner §A1 / §A6. MODE=fixture bypasses dedup (re-runnable).
Capture $WORKDIR first so every write lands in the real repo, not the throwaway target. Every mode works inside gitignored .scan/, so the build artifacts (out/, cache/, crytic-export/, fuzz corpus/) never touch the tracked tree.
WORKDIR="$(git rev-parse --show-toplevel)" # aeon repo root - memory/ and state live here
mkdir -p "$WORKDIR/.scan"
if [ "$MODE" = fixture ]; then
# Local regression: copy the bundled fixture into gitignored .scan/ and audit the COPY
# (never the tracked fixture) so forge's out/cache stay out of the working tree. No fork.
SRC="$WORKDIR/skills/sc-audit/fixtures/$FIXTURE"
[ -d "$SRC" ] || { echo "no-fixture: $FIXTURE"; exit 0; }
rm -rf "$WORKDIR/.scan/$FIXTURE"; cp -r "$SRC" "$WORKDIR/.scan/$FIXTURE"
# If the sandbox refuses `cp`, replicate the fixture files with the Read/Write tools instead
# and verify byte-identical with `diff -r "$SRC" "$WORKDIR/.scan/$FIXTURE"`.
cd "$WORKDIR/.scan/$FIXTURE"
elif [ "$MODE" = onchain ]; then
# Live contract by address: fetch the VERIFIED source from the explorer/Sourcify and
# materialize it as a Foundry project under .scan/. See §S2b for the fetch + materialize +
# on-chain-context steps; it lands you in the project dir. If no verified source exists,
# §S2b exits clean (bytecode-only audit is out of scope).
PROJ="$WORKDIR/.scan/onchain-$CHAIN-$ADDR"
echo "onchain project dir: $PROJ (materialize per §S2b, then cd there)"
# >>> run §S2b here <<< - after it, you are in "$PROJ" with src/ + foundry.toml written.
else
cd "$WORKDIR/.scan"
gh repo fork "$REPO" --clone --default-branch-only -- --depth 50 --quiet
cd "$(basename "$REPO")" # now in <workdir>/.scan/<repo>
fi
# --- Scratch dir for this run's intermediate files (scan JSON, sources.txt, fuzz harness).
# Prefer /tmp; fall back to a gitignored dir beside the clone under .scan/ when the skill
# sandbox blocks /tmp. RE-RUN these three lines at the top of every later Bash block that
# touches scratch (S4, S6.5) - claude -p spawns a FRESH shell per Bash call, so $SCRATCH does
# NOT persist (cwd does, shell vars don't). `$(cd .. && pwd)` is .scan/ (you're in .scan/<target>).
SCRATCH=/tmp/sc-audit
mkdir -p "$SCRATCH" 2>/dev/null && [ -w "$SCRATCH" ] || SCRATCH="$(cd .. && pwd)/_sc-audit"
mkdir -p "$SCRATCH"; echo "scratch: $SCRATCH"
# Confirm Solidity actually present (auto-select already filtered, but a direct $REPO may not have):
if ! ls **/*.sol >/dev/null 2>&1 && [ -z "$(find . -name '*.sol' -not -path '*/node_modules/*' 2>/dev/null | head -1)" ]; then
echo "no-solidity after clone: $REPO" # log a clean no-op row in S8 and exit
fiMODE=onchain only)Run this only when MODE=onchain. It fetches the contract's verified source, writes it as a Foundry project under $PROJ, and records on-chain context that drives severity. If no verified source exists, log it and exit clean - a bytecode-only audit is out of scope (decompilation is unreliable and would produce unfalsifiable findings; the honest output is "source not verified, cannot audit").
1. Fetch the verified source. Prefer Etherscan V2 (one key, ~60 chains, best coverage); fall back to Sourcify (keyless), then to Blockscout's keyless REST for Blockscout-explorer chains that are on neither. The Etherscan key goes through ./secretcurl as a {ETHERSCAN_API_KEY} placeholder (it ends _KEY, so it substitutes) - never put the raw key on the command line. Presence-check the key with ${VAR:+x}, not a bare $VAR (a bare secret expansion is blocked by the Bash layer):
SCRATCH=/tmp/sc-audit; mkdir -p "$SCRATCH" 2>/dev/null && [ -w "$SCRATCH" ] || SCRATCH="$WORKDIR/.scan/_sc-audit"; mkdir -p "$SCRATCH"
# Blockscout base URL for chains NOT on Etherscan V2 (keyless REST). One line per chain.
case "$CHAIN" in
*) BLOCKSCOUT="" ;;
esac
VERIFIED=no
# (a) Etherscan V2 getsourcecode - only if a key is configured
if [ -n "${ETHERSCAN_API_KEY:+x}" ]; then
./secretcurl -s -w 'http=%{http_code}\n' -o "$SCRATCH/etherscan.json" \
"https://api.etherscan.io/v2/api?chainid=$CID&module=contract&action=getsourcecode&address=$ADDR&apikey={ETHERSCAN_API_KEY}"
# verified iff result[0].ABI is real source (NOT the literal "Contract source code not verified")
if python3 - "$SCRATCH/etherscan.json" <<'PY'
import json,sys
try: r=json.load(open(sys.argv[1]))["result"][0]
except Exception: sys.exit(1)
sys.exit(0 if r.get("ABI","").strip() and r["ABI"]!="Contract source code not verified" and r.get("SourceCode","").strip() else 1)
PY
then VERIFIED=etherscan; fi
fi
# (b) Sourcify keyless fallback (no key, or Etherscan had no verified source).
# Use API **v2**. The old v1 route (/server/files/any/$CID/$ADDR) is deprecated; do not fall
# back to it. v2 returns ONE object (not a file list) whose `sources` maps path -> {content},
# so it needs its own parse branch below.
if [ "$VERIFIED" = no ]; then
curl -s -o "$SCRATCH/sourcify.json" \
"https://sourcify.dev/server/v2/contract/$CID/$ADDR?fields=sources,compilation,proxyResolution,deployment" \
2>/dev/null || true
grep -q '"sources"' "$SCRATCH/sourcify.json" 2>/dev/null && VERIFIED=sourcify || true
fi
# (c) Blockscout v2 fallback - Blockscout-explorer chains that are on NEITHER Etherscan V2 nor
# Sourcify. Keyless GET /api/v2/smart-contracts/<addr> returns ONE object: source_code + file_path
# + additional_sources[{file_path,source_code}] + proxy_type + implementations
# + compiler_version/evm_version. Parsed by its own branch in step 2.
if [ "$VERIFIED" = no ] && [ -n "$BLOCKSCOUT" ]; then
curl -s -o "$SCRATCH/blockscout.json" "$BLOCKSCOUT/api/v2/smart-contracts/$ADDR" 2>/dev/null || true
if python3 - "$SCRATCH/blockscout.json" <<'PY'
import json,sys
try: d=json.load(open(sys.argv[1]))
except Exception: sys.exit(1)
sys.exit(0 if d.get("is_verified") and (d.get("source_code") or "").strip() else 1)
PY
then VERIFIED=blockscout; fi
fi
echo "verified-source: $VERIFIED"
if [ "$VERIFIED" = no ]; then
echo "onchain: source NOT verified for $CHAIN:$ADDR - cannot audit (bytecode-only out of scope)."
# Write a clean dedup row (channel: skipped) + coverage note in S8, notify nothing, exit.
fi2. Materialize the Foundry project at $PROJ (src/ + a synthesized foundry.toml). Etherscan's SourceCode field has three shapes - a plain flattened string, a single-brace { "File.sol": {"content": ...} } map, or a double-brace {{ ...standard-json... }} object - so parse with Python, not jq. This snippet handles all three plus the Sourcify file list, and pins solc/evm_version from the compiler metadata:
python3 - "$SCRATCH" "$PROJ" "$VERIFIED" <<'PY'
import json, os, re, sys
scratch, proj, src_from = sys.argv[1], sys.argv[2], sys.argv[3]
os.makedirs(os.path.join(proj, "src"), exist_ok=True)
def write(relpath, content):
p = os.path.join(proj, "src", relpath.lstrip("/"))
os.makedirs(os.path.dirname(p) or os.path.join(proj, "src"), exist_ok=True)
open(p, "w").write(content)
solc = evm = name = ""; proxy = "0"; impl = ""
if src_from == "etherscan":
r = json.load(open(os.path.join(scratch, "etherscan.json")))["result"][0]
name = r.get("ContractName") or "Contract"; proxy = str(r.get("Proxy", "0")); impl = r.get("Implementation", "")
m = re.search(r"v?(\d+\.\d+\.\d+)", r.get("CompilerVersion", "")); solc = m.group(1) if m else ""
ev = (r.get("EVMVersion") or "").strip().lower(); evm = "" if ev in ("", "default") else ev
s = (r.get("SourceCode") or "").strip()
if s.startswith("{{") and s.endswith("}}"):
for path, v in json.loads(s[1:-1]).get("sources", {}).items(): write(path, v.get("content", ""))
elif s.startswith("{"):
obj = json.loads(s)
srcs = obj.get("sources", obj)
for path, v in srcs.items():
if isinstance(v, dict) and "content" in v: write(path, v["content"])
else:
write(f"{name}.sol", s)
elif src_from == "sourcify": # sourcify API v2 - ONE object: sources{path:{content}} + compilation + proxyResolution
d = json.load(open(os.path.join(scratch, "sourcify.json")))
for path, v in (d.get("sources") or {}).items():
write(path, v.get("content", "") if isinstance(v, dict) else v)
comp = d.get("compilation") or {}
name = comp.get("name") or "Contract"
m = re.search(r"(\d+\.\d+\.\d+)", comp.get("compilerVersion", "")); solc = m.group(1) if m else solc
ev = ((comp.get("compilerSettings") or {}).get("evmVersion") or "").strip().lower()
evm = "" if ev in ("", "default") else ev
pres = d.get("proxyResolution") or {}
if pres.get("isProxy"):
proxy = "1"
impls = pres.get("implementations") or []
# v2 implementation entries are dicts ({"address": ...}) or bare strings
if impls: impl = impls[0].get("address", "") if isinstance(impls[0], dict) else impls[0]
else: # blockscout v2 - /api/v2/smart-contracts: source_code + file_path + additional_sources[]
d = json.load(open(os.path.join(scratch, "blockscout.json")))
if d.get("file_path"): write(d["file_path"], d.get("source_code") or "")
else: write(f'{d.get("name") or "Contract"}.sol', d.get("source_code") or "")
for a in (d.get("additional_sources") or []):
if a.get("file_path"): write(a["file_path"], a.get("source_code") or "")
name = d.get("name") or "Contract"
m = re.search(r"(\d+\.\d+\.\d+)", d.get("compiler_version", "")); solc = m.group(1) if m else solc
ev = (d.get("evm_version") or "").strip().lower(); evm = "" if ev in ("", "default") else ev
pt = d.get("proxy_type")
if pt and str(pt).lower() not in ("none", "unverified"):
proxy = "1"
impls = d.get("implementations") or []
if impls: impl = impls[0].get("address", "") if isinstance(impls[0], dict) else impls[0]
ft = ['[profile.default]', 'src = "src"', 'out = "out"', 'libs = ["lib"]']
if solc: ft.append(f'solc = "{solc}"')
if evm and evm != "default": ft.append(f'evm_version = "{evm}"')
open(os.path.join(proj, "foundry.toml"), "w").write("\n".join(ft) + "\n")
json.dump({"name": name, "solc": solc, "evm": evm, "proxy": proxy, "implementation": impl},
open(os.path.join(scratch, "materialized.json"), "w"))
print(f"materialized: name={name} solc={solc} evm={evm} proxy={proxy} impl={impl}")
PY
cd "$PROJ"3. If it's a proxy, also materialize the implementation - the proxy shell holds almost no logic; the bug is in the implementation it delegatecalls. When materialized.json has proxy=1 and a non-empty implementation, re-run steps 1-2 for that implementation address (fetch impl on the same $CID, write it under src/impl/) so S4/S5 audit the real logic. Note the proxy->impl relationship prominently in the report. If the explorer didn't flag a proxy, still probe the EIP-1967 implementation slot before concluding it's non-upgradeable (step 4).
4. Record on-chain context (drives severity - a live contract holding funds turns a "possible" into "exploitable now"). Best-effort via the Etherscan V2 proxy/account modules (same key), Blockscout's keyless Etherscan-compatible /api for Blockscout chains, or a public RPC; each is optional, never fatal. Write $SCRATCH/onchain-context.json:
if [ -n "$BLOCKSCOUT" ]; then
# Blockscout chains: Etherscan V2 does not cover $CID, so read the native balance from
# Blockscout's keyless Etherscan-compatible /api. Its result is a wei string in the SAME
# {"status","message","result"} shape as Etherscan V2's balance response, so it parses
# identically into native_balance_wei. Proxy/impl already came from the materialize step.
# An optional BLOCKSCOUT_API_KEY is passed via secretcurl's {BLOCKSCOUT_API_KEY} placeholder -
# the legacy /api honors ?apikey= on key-enforcing instances; instances that don't enforce
# keyed tiers ignore it. Keyless otherwise. The v2 source endpoint above ignores the key.
if [ -n "${BLOCKSCOUT_API_KEY:+x}" ]; then
./secretcurl -s -o "$SCRATCH/balance.json" \
"$BLOCKSCOUT/api?module=account&action=balance&address=$ADDR&tag=latest&apikey={BLOCKSCOUT_API_KEY}"
else
curl -s -o "$SCRATCH/balance.json" \
"$BLOCKSCOUT/api?module=account&action=balance&address=$ADDR&tag=latest" 2>/dev/null || true
fi
elif [ -n "${ETHERSCAN_API_KEY:+x}" ]; then
# native balance at the address = funds directly at risk (wei)
./secretcurl -s -o "$SCRATCH/balance.json" \
"https://api.etherscan.io/v2/api?chainid=$CID&module=account&action=balance&address=$ADDR&tag=latest&apikey={ETHERSCAN_API_KEY}"
# EIP-1967 implementation slot (proxy detection independent of the explorer flag)
./secretcurl -s -o "$SCRATCH/eip1967.json" \
"https://api.etherscan.io/v2/api?chainid=$CID&module=proxy&action=eth_getStorageAt&address=$ADDR&position=0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc&tag=latest&apikey={ETHERSCAN_API_KEY}"
fiCapture into $SCRATCH/onchain-context.json: {chain, cid, address, name, solc, evm_version, is_proxy, implementation, native_balance_wei, admin_or_owner (best-effort), notes}. Also note the ERC20/TVL exposure qualitatively if it's obvious from the contract type (a router/vault/bridge holding user funds is high-exposure). This context is non-sensitive (it does not aid exploitation) and rides into the S9 report and the staged disclosure so the operator sees the blast radius. From here, S3-S6.5 run unchanged against $PROJ - the materialized source is just another Foundry project in .scan/.
slither, solc, solc-select, forge, crytic-compile, echidna, medusa are the contract toolchain. Every step self-guards with command -v, so any tool that is not present is skipped, never fatal - the source pass (S5) is the reliable core and needs none of them. Staging splits two ways:
slither-analyzer, solc-select (downloads solc), and crytic-compile install via the allow-listed python3 -m pip. This covers Slither on hardhat (via npx) and bare-solc layouts.forge, echidna, medusa ship as tarballs/binaries whose installers need shell tooling that may not be allow-listed in-run, so they are only available when the instance provides a pre-claude -p staging step that installs them and appends their dir to $GITHUB_PATH. When no such step ran or a tool failed to stage: a foundry repo may not build (Slither degrades to compile-fail, source pass S5 still runs), and the fuzz arm (S6.5) is skipped cleanly.Harden first - you are about to compile and (in S6.5) fuzz UNTRUSTED contract code. Compiling and running a foundry test executes the target repo's build and test code in this runner. Disable foundry FFI so a malicious foundry.toml/test can't shell out to the host, and only ever work inside the throwaway .scan/<repo> clone.
export FOUNDRY_FFI=false # block forge tests from shelling out to the host
export PATH="/tmp/bin:$HOME/.local/bin:/usr/local/bin:$HOME/.foundry/bin:$PATH"
# In-run pip tools (crytic-compile drives the build for slither/echidna/medusa):
command -v slither >/dev/null 2>&1 || python3 -m pip install --quiet --disable-pip-version-check slither-analyzer 2>/dev/null || true
command -v solc-select >/dev/null 2>&1 || python3 -m pip install --quiet --disable-pip-version-check solc-select 2>/dev/null || true
command -v crytic-compile >/dev/null 2>&1 || python3 -m pip install --quiet --disable-pip-version-check crytic-compile 2>/dev/null || true
# forge/echidna/medusa: use them only if a workflow step already staged them (command -v guards below).Pick the contract's solc version when a compile fails on version mismatch: read the pragma solidity line and solc-select install <ver> && solc-select use <ver> (solc-select downloads the binary via Python - this works in-run). crytic-compile auto-detects hardhat (npx hardhat), foundry (forge, if staged), and bare-solc layouts.
Cap the compile - the S5 source pass needs NO compiler, so NEVER chase a clean build. Slither and the fuzz arm are best-effort extras; the audit's reliable core (S5) reads source and needs nothing to build. So make at most ONE best-effort compile attempt (plus one optional solc-select switch to match the pragma), and time-box it to a few minutes. If it still fails, set SLITHER=compile-fail, skip Slither and the fuzz arm, and go straight to S5. Do NOT npm install/forge install missing deps, hand-write remappings.txt, add lib/ submodules, or loop the compile trying to satisfy imports - that rabbit hole is the #1 cause of a run hitting the job timeout and losing the whole scan. This bites on-chain / MODE=onchain targets hardest: materialized explorer/Sourcify source routinely imports @openzeppelin, @layerzerolabs, etc. with no remappings or lib/, so it will not build out of the box - that is expected, record compile-fail and audit the source directly. A self-contained contract (e.g. WETH) compiles instantly; a dep-heavy one won't - do not spend the run's budget forcing it.
# Fresh shell - re-resolve $SCRATCH (see S2): prefer /tmp, else beside the clone under .scan/.
SCRATCH=/tmp/sc-audit; mkdir -p "$SCRATCH" 2>/dev/null && [ -w "$SCRATCH" ] || SCRATCH="$(cd .. && pwd)/_sc-audit"; mkdir -p "$SCRATCH"
if command -v slither >/dev/null 2>&1; then
slither . --json $SCRATCH/slither.json --exclude-informational --exclude-low 2>$SCRATCH/slither.err || true
fi
# Status: ok = wrote JSON with results; compile-fail = ran but crytic-compile could not build
# (wrong solc version, missing deps, non-standard layout) - try `solc-select install/use <pragma
# version>` once, then re-run; skipped = binary absent. compile-fail is NOT a network/sandbox block.
if [ -s $SCRATCH/slither.json ]; then SLITHER=ok
elif command -v slither >/dev/null 2>&1; then SLITHER=compile-fail
else SLITHER=skipped; fi
echo "slither=$SLITHER" > $SCRATCH/sources.txtOn compile-fail, the source pass (S5) still carries the audit - it needs no compiler. Never report slither=fail with a "sandbox / network" reason; the real reason is a build problem in the target (record the shortest decisive line from slither.err).
Build the spec of what must be true before you go looking for what's wrong. A freeform read finds the bugs you happen to trip over; a threat model finds the bugs that matter because you decided what "broken" means up front. This is the same "threat-model before analysis" discipline used by professional audit workflows - it focuses the attack surface, and the invariant list it produces is reused twice: it targets the S5 hunt and it becomes the S6.5 fuzz properties. This is fully automated - you (the agent) derive it from the source; there is no human step.
Read the production contracts and the on-chain context (for MODE=onchain, the §S2b proxy/owner/funds data), then write $SCRATCH/threat-model.json:
sum(balanceOf) <= totalDeposited), solvency (assets >= liabilities), share/asset ratio monotonicity, supply caps, access (only owner can pause), one-time init, no-free-value. Give each a stable id.{
"actors": [{"who":"anyone","trusted_to":"deposit/withdraw own funds"},
{"who":"owner","trusted_to":"pause, set fees"}],
"invariants": [{"id":"INV1","statement":"sum(balanceOf[*]) <= totalDeposited","why":"insolvency = theft"},
{"id":"INV2","statement":"only owner can pause","why":"griefing / fund lock"}],
"assets_at_risk": ["user deposits (native balance)", "protocol ownership"]
}Keep it tight - a handful of load-bearing invariants beats an exhaustive list. This model is the audit's plan; S5 hunts for a path that breaks any listed invariant, and S6.5 fuzzes each one.
You are the agentic scanner. Read the Solidity source and, guided by the S5.0 threat model, reason about who can call what, what state moves, and which invariant breaks - the source->sink work Slither can't do. This pass produces candidates, not verdicts; everything goes through S6 triage.
Bound it. Size the contract surface, then deep-review the top-N highest-exposure contracts only; record the rest as reviewed: false (they still count in the S8 coverage denominator).
SOL_FILES=$(find . -name '*.sol' -not -path '*/node_modules/*' -not -path '*/lib/*' \
-not -path '*/test/*' -not -path '*/tests/*' -not -path '*/mock*/*' 2>/dev/null | wc -l | tr -d ' ')
if [ "${SOL_FILES:-0}" -le 15 ]; then N=$SOL_FILES # small - review all production contracts
elif [ "${SOL_FILES:-0}" -le 60 ]; then N=12
else N=8; fi
echo "sc-budget: SOL_FILES=$SOL_FILES N=$N"1. Inventory the attack surface. Enumerate every external/public state-changing function and every value-handling path. Rank by exposure: unauthenticated + reachable + moves funds/critical state first. Ignore test/, mock/, script/, lib/ (dependencies), and *.t.sol.
2. Deep-review the top N - hunt for a path that breaks a listed invariant. For each entrypoint, trace attacker control -> state change -> which S5.0 invariant it could violate (targeted, not freeform). Also run this Solidity-specific checklist (the classes Slither is weak on) - a hit here often reveals an invariant break you didn't list, so add it to the threat model:
initialize, tx.origin auth, owner-only funcs left external, role-grant paths, upgrade authorization.nonReentrant presence.updatedAt/round check), flash-loan-manipulable pricing.unchecked blocks, decimals mismatch, ERC4626 first-depositor / share-inflation.selfdestruct/delegatecall in an implementation, selector clashes.DoS via revert-in-loop, gas griefing.chainId in the EIP-712 domain, malleable ecrecover, missing deadline, permit misuse.balanceOf-accounting attacks.IHooks callback (before/after Initialize/Swap/AddLiquidity/RemoveLiquidity/Donate) or inherits a hook base (BaseHook, SafeCallback, IHooks, a custom fee base), it is a v4 hook regardless of origin (any repo, any deployed address, any file - this is target-agnostic). Audit it against the FULL 11-class checklist in skills/sc-audit/references/hook-checklist.md: access control (Cork $12M missing onlyPoolManager), hookData/PoolKey validation (Cork, Doppler), flash-accounting deltas (settle/sync/take/clear + BeforeSwapDelta sign, CELO double-settle), rounding/precision (Bunni $8.4M), reentrancy (Bunni), permission-bit vs address-flag encoding (Angstrom), dynamic fees, JIT liquidity (OZ LiquidityPenalty), tick-crossing/price manipulation (Angstrom, Flayer), gate unit-confusion, and DoS/economic. Derive the hook's permission flags from the address (low 14 bits, mask 0x3FFF) or getHookPermissions() and cross-check they match the implemented callbacks. Two source-reasoning gate cases worth stating inline: a beforeSwap/afterSwap gate that compares raw amountSpecified to a token-denominated constant is unit-confused: the caller chooses the specified currency via exact-in vs exact-out AND swap direction, so one constant silently governs BOTH tokens of the pool, and on a sub-18-decimal specified token the cap fails OPEN (never binds). A balance / skew / heavier-side gate on the two virtual reserves is a raw-price-vs-1.0 gate in disguise: StateLibrary gives amount0 = L*2^96/sqrtP and amount1 = L*sqrtP/2^96, so amount0/amount1 = 1/price and the liquidity L cancels, leaving the pool's raw price (token1/token0 in smallest units) compared to an implicit 1.0. Such a gate is permanently one-directional on any pair that is not a same-decimals pool near parity (one whole leg reverts forever) unless it anchors to the pool's OWN reference - its sqrtPriceX96 at afterInitialize (adds flag bit 0x1000), or an explicit target ratio. Confirm by reasoning at a price away from 1:1, BOTH legs: a gate that only looks correct at parity is a false pass.sum(shares) == totalSupply, collateral >= debt, accounting conservation) and reason whether any reachable path breaks one.MODE=onchain) - explorer/Sourcify verification proves source == bytecode, it proves nothing about source == genuine upstream. Most verified contracts are 90%+ vendored @openzeppelin / @layerzerolabs / solmate files, so one altered line inside a vendored file is invisible both to the explorer and to a reviewer who assumes "that's just OpenZeppelin" - the highest-yield backdoor there is, and the cheapest to rule out. Identify the upstream versions, pull the genuine releases (curl -sL https://registry.npmjs.org/<pkg> for the tarball URL - URL-encode the whole scoped name, both the @ scope and the / separator (@openzeppelin/contracts becomes %40openzeppelin%2Fcontracts) - then extract with Python's tarfile; the tar binary may not be allow-listed), and diff every vendored file by SHA-256. Report a per-file IDENTICAL / DIFFERENT / UNVERIFIED verdict and never mark a file clean you could not actually compare. A near-miss on the version is normal: sweep the adjacent releases before calling a diff "tampering". Pair it with a bytecode match - compile locally and compare against eth_getCode, masking immutables and the trailing CBOR metadata - which upgrades "this source is verified" into "this source is provably the live code".3. Emit candidates. Write one JSON array (may be []) to $SCRATCH/agentic.json with the Write tool, then record the source status:
[
{"file":"src/Vault.sol","line":142,"severity":"critical","category":"access-control",
"claim":"initialize() has no initializer guard - anyone can re-init and set themselves owner",
"invariant":"only deployer is owner after setup","invariant_id":"INV2"}
]echo "agentic=ok" >> $SCRATCH/sources.txt # 0 candidates on a reviewed surface is still `ok`;
# `agentic=skipped` only if source is unreadable (bytecode-only)Tag each candidate with the invariant it breaks and the invariant_id from the S5.0 model (or a new id if the checklist surfaced one not yet listed). A candidate whose category in {invariant, arithmetic/precision, economic, access-control} is a fuzz-arm target (S6.5): S6.5 turns its stated invariant into a property test.
Merge $SCRATCH/slither.json and $SCRATCH/agentic.json and triage every candidate by the same bar as vuln-scanner §A4:
lib/.If 0 findings survive -> log "clean audit - N candidates reviewed, 0 confirmed" and go to S8/S9.
Then adversarially verify every survivor before S7 - apply vuln-scanner §A4.5. Switch sides and try to refute each finding: re-read the contract at HEAD (confirm the vulnerable code is still present and unguarded on the default branch / at the audited commit), build the complete precondition chain (who calls it, with what, which guard/modifier is bypassed - one missing link refutes), and hunt the mitigating control you missed (a modifier, a require, an OZ guard, a non-reachable path). Only a finding that survives refutation is CONFIRMED and disclosable. For an invariant/economic/precision/access-control finding, the S6.5 fuzz arm is the machine-proof form of this refutation - a shrunk counterexample IS the confirmation; a clean bounded fuzz run does not refute (see S6.5). Record the verified/refuted tally in the coverage manifest (S8) and the report (S9).
A property fuzzer turns a claimed invariant break into machine-proven evidence: it searches millions of call sequences for one that violates the invariant and hands back the exact counterexample calls. A fuzzer-found counterexample is the strongest repro you can attach to a private disclosure - it removes all doubt that the flaw is real and reachable.
HARD GATE - a clean audit NEVER fuzzes. If S6 confirmed 0 findings, skip this entire section - do not write a harness, do not compile, do not launch echidna or medusa - go straight to S8. Fuzzing exists only to prove a finding that already survived triage; on a clean audit a fuzzer just burns its full test budget finding nothing, and two campaigns at their timeouts can alone blow the job timeout (which loses the whole scan - findings, ledger row, everything). When in doubt, skip - the S5 reasoning is the audit; the fuzz arm is an optional bonus proof, never a requirement.
Run this arm only when ALL hold (it is optional and bounded - never let it block the disclosure):
category in {invariant, arithmetic/precision, economic, access-control} and a stated invariant. 0 survivors -> skip the arm entirely (see the hard gate above).echidna or medusa staged, and the repo compiled (SLITHER=ok, i.e. crytic-compile can build it). A repo that won't compile can't be fuzzed - skip the arm, keep the S5 reasoning as the repro. (Per S3, an on-chain / external-import target that won't build on one attempt is compile-fail -> skip the arm, do not force a build to enable fuzzing.)FOUNDRY_FFI=false is exported (S3) - do not fuzz a repo whose tests need FFI.timeout 480 per fuzzer, below), and if several findings qualify, fuzz only the single highest-severity one.Steps:
Write a minimal property harness to $SCRATCH/harness/ - a contract that deploys the target, exposes the attacker-reachable entrypoints, and asserts the finding's invariant (from the S5.0 threat model, invariant_id) as an Echidna/Medusa property (a function echidna_<name>() public returns (bool) that returns false when the invariant breaks, or a Medusa assertion). Keep it minimal; import the target contracts from the clone. Do not modify the target source - the harness is separate.
Then prove the harness REACHES the code it claims to test - before you trust any result from it. A
property that is never driven into the dangerous path passes trivially, on a fully-spent budget, and is
indistinguishable from a clean result. This is a different failure from the zero-budget one in step 3: the
fuzzer ran millions of calls, the counters look healthy, and the action was a silent no-op the whole time.
For each action that is supposed to hit a guarded path, add a witness: a deterministic forge test (or a
harness counter the campaign exposes) asserting the path was actually entered - the credit landed, the
callback ran, the guarded call reverted for the expected reason. If a witness fails, the harness is broken,
not the contract. General rule: a low-level .call / hand-written abi.encodeWithSignature that silently
returns false, and any callee whose interface declares it view or pure (the caller then compiles the
call site to STATICCALL, so a storage write inside it reverts and the guarded path is never exercised), are
the two places a harness action quietly becomes a no-op while the fuzzer reports every property held.
Fuzz, time-boxed. Bound the run so it can't hang the skill:
# Fresh shell - re-resolve $SCRATCH (see S2): prefer /tmp, else beside the clone under .scan/.
SCRATCH=/tmp/sc-audit; mkdir -p "$SCRATCH" 2>/dev/null && [ -w "$SCRATCH" ] || SCRATCH="$(cd .. && pwd)/_sc-audit"; mkdir -p "$SCRATCH"
mkdir -p $SCRATCH/harness
# Echidna (property mode), bounded test + shrink budget.
# Echidna REFUSES solc < 0.4.25 and exits ~instantly having run nothing. On-chain targets are
# routinely older than that (mainnet WETH9 is 0.4.19), so on a pre-0.4.25 target this is a TOOL
# LIMITATION, not a clean property - record it as such and lean on medusa, which has no such
# floor. Never let that near-zero-duration exit become a "no counterexample".
timeout 480 echidna $SCRATCH/harness/Invariant.sol --contract InvariantTest \
--test-limit 25000 --shrink-limit 5000 --format text > $SCRATCH/echidna.txt 2>&1 || true
# OR Medusa (property mode). Medusa is CONFIG-DRIVEN - `medusa init --out <path>` then patch
# the JSON. There is no CLI flag for testPrefixes (nor for assertion mode), so property-mode
# runs need the config file; `--target` and `--assertion-mode` are NOT valid flags (1.5.1).
medusa init --out $SCRATCH/medusa.json 2>/dev/null || true
python3 - "$SCRATCH/medusa.json" "$SCRATCH/harness" <<'PY'
import json, sys
p = sys.argv[1]; c = json.load(open(p)); f = c["fuzzing"]; t = f["testing"]
f["testLimit"] = 25000; f["callSequenceLength"] = 10
f["targetContracts"] = ["InvariantTest"]
f["targetContractsBalances"] = ["0x3635c9adc5dea00000"] # 1000 ETH, so deposits are affordable
t["propertyTesting"]["enabled"] = True
t["propertyTesting"]["testPrefixes"] = ["property_", "echidna_"]
t["assertionTesting"]["enabled"] = False
t.setdefault("optimizationTesting", {})["enabled"] = False
# MANDATORY: `medusa init` writes this true, which halts the WHOLE fuzzer on the first
# falsified property and then reports every remaining property as [PASSED] on a zero-call
# budget - a false negative that reads exactly like a clean property. Always force it false.
t["stopOnFailedTest"] = False
# Medusa resolves a RELATIVE compilation target against the CONFIG FILE's directory,
# not your cwd. With the config in $SCRATCH, a "." target silently compiles $SCRATCH
# (no .sol there) and then dies with "<Contract> was specified in the target contracts
# but was not found in the compilation artifacts". Always give an ABSOLUTE path.
c["compilation"]["platformConfig"]["target"] = sys.argv[2]
json.dump(c, open(p, "w"), indent=2)
PY
timeout 480 medusa fuzz --config $SCRATCH/medusa.json > $SCRATCH/medusa.txt 2>&1 || true
echo "fuzz=$([ -s $SCRATCH/echidna.txt ] || [ -s $SCRATCH/medusa.txt ] && echo ok || echo skipped)" >> $SCRATCH/sources.txtfuzz=skipped is ambiguous - check why before you record it. The || true above swallows a
broken command just as quietly as a missing binary, so an invalid flag and an unstaged tool both land
as skipped. Read $SCRATCH/medusa.txt: unknown flag: means the command is wrong (fix it and
re-run), not that the arm was unavailable. Only report skipped when the tool genuinely isn't staged
or the repo wouldn't compile.
Read the result. A failed!/FAILED/counterexample block = the invariant is broken - extract the minimal call sequence the fuzzer shrank to, and attach it (privately) as the repro in S7. A clean run (all properties held within budget) does not disprove the finding - it means the fuzzer did not find a counterexample in the budget; keep the S5 reasoning and say so honestly ("fuzzed 50k tests, no counterexample within budget; manual analysis stands"). Never upgrade or downgrade a severity purely on a bounded fuzz result.
Before you write "held within budget", confirm the budget was actually spent. A property is only
evidence of anything if the fuzzer really ran it. Check the reported call/test count on the passing
property, not just the campaign total: medusa printing [PASSED] next to calls: 0 at elapsed: 0s
means it stopped early (see stopOnFailedTest above), not that the property holds. A passing property
with a near-zero budget is a false negative; re-run it with a real budget or record it as not-exercised.
A clean campaign is only worth reporting if it came with a NEGATIVE CONTROL. Before writing up "N/N
properties held", copy the target source to a throwaway project, mutate exactly one trust boundary (delete
a require/revert, drop an onlyOwner, credit one extra wei), and re-run the identical harness. If the
mutant is not falsified, your properties have no teeth and the clean run means nothing - fix the harness and
re-run both. Prefer two mutants breaking different boundaries: they should produce different failure
signatures, which is what shows the properties discriminate rather than all firing on any perturbation. The
negative control is also the cheapest way to catch a step-1 reachability defect - a mutant that fails to fire
is usually a harness that never reached the code, not a contract that is safe.
Record what was NOT exercised. Report per-file line coverage from the fuzzer (medusa writes
crytic-export/coverage/lcov.info relative to $SCRATCH, not to the harness dir) and name the paths the
campaign never touched. "6/6 held" over a surface where a whole branch was never entered is a partial result;
say which part.
Never publish the counterexample publicly before a fix ships - a shrunk fuzz counterexample IS a working exploit sequence. It goes only into the private PVR/email body (S7), per STRATEGY.md.
MODE=fixture -> skip this whole section. A fixture is a deliberately-vulnerable regression target with no maintainer and nothing to fix; never disclose it. Record the confirmed finding + the fuzz counterexample in the run report (§S9) and stop.
MODE=onchain -> operator-gated disclosure only. NEVER auto-file a PVR, auto-send an email, or open any public channel. A live contract that holds funds is the highest-stakes disclosure this skill makes, and an automated false report against a real protocol is far more damaging than a missed bug. So an on-chain finding is always staged for the operator, never actioned in-run:
SECURITY.md / security.txt intake is next only if no bounty exists.security@ address on the project site, a documented multisig/admin contact, or an ENS/security.txt.$WORKDIR/memory/pending-disclosures/onchain-$CHAIN-$ADDR-<ts>.md with auto_send: false and human_only: true (the arming gate cannot fire - see vuln-scanner §A5b draft format), severity:, portal_url: (the resolved Immunefi/PSIRT intake if any), and a body carrying: the finding, the invariant it breaks, the on-chain context from §S2b step 4 (chain, address, proxy/impl, native balance / funds at risk), and the private repro (S6.5 counterexample if any). Never put a fund-draining call sequence anywhere public.channel as pending-disclosure (or portal if an Immunefi/PSIRT URL resolved).The rest of this section (the shared machinery below) applies to MODE=repo. Same bounty-first rule: search Immunefi / Sherlock / Cantina / HackenProof / Code4rena before reading PVR-enabled as permission to auto-file. A live bounty = stage a portal draft (status: pending-operator-send, portal_url:, auto_send: false, human_only: true) and notify. Never POST a PVR when a bounty exists. PVR is the fallback only when that search returns nothing AND SECURITY.md does not name a portal.
Do not re-implement disclosure here. Contract findings (MODE=repo) route through vuln-scanner's shared machinery after the bounty search above. Read and follow skills/vuln-scanner/SKILL.md:
SECURITY.md (and the org-level {owner}/.github fallback). The designated intake wins over PVR. Do not trust a README "bug bounty coming soon" line - search the five platforms above. A live Immunefi / Sherlock / Cantina / HackenProof / Code4rena program is the intake, not GitHub PVR. Stage a portal draft for the operator (status: pending-operator-send, portal_url:, auto_send: false); never substitute GitHub PVR as a backup./reports payload mechanics (the non-empty vulnerabilities array is mandatory or the API 500s; write the advisory body to a temp file first). Set ecosystem on the affected package sensibly; for a raw contract repo with no package manifest, use {"package":{"ecosystem":"other","name":"<repo>"}}.@openzeppelin/contracts version flagged by osv against a lockfile) does the public-PR-bump path apply. Pure contract-logic bugs never go to a public PR.When you write the advisory/report body, obey STRATEGY.md: lead with the finding, the invariant it breaks, and a clean repro; never publish a working exploit or fund-draining call sequence before a fix ships. Describe the flaw and the fix, not a copy-paste attack.
Mandatory on every MODE=repo and MODE=onchain run (clean, skip, or finding), written to the absolute $WORKDIR path - a bare memory/... lands in the throwaway clone. MODE=fixture writes NO ledger row (fixtures must stay re-runnable - a row would dedup-block the next regression run); record the fixture result only in the log (§S9). The coverage manifest below is still worth writing for a fixture (use slug fixture-$FIXTURE).
Append to $WORKDIR/memory/vuln-scanned.json the same row shape as vuln-scanner §A6: {"repo","scanned_at","findings":<N>,"channel":"pvr|portal|email|pending-disclosure|public-pr|clean|skipped"}. For MODE=onchain the repo key is onchain:$CHAIN:$ADDR (the same key S1 dedups on). A no-solidity / no-solidity-target / on-chain unverified exit still writes a clean (or skipped) row so it isn't re-picked tomorrow.
Write the coverage manifest to $WORKDIR/memory/coverage/<slug>-${today}.json (slug = repo with /->-, or onchain-$CHAIN-$ADDR for on-chain). Coverage is non-sensitive (it names what was reviewed, not how to exploit it), so it ships even when finding detail is redacted, and it is the baseline a future rescan diffs against. Shape:
{
"repo": "owner/repo",
"scanned_at": "<ISO-8601>",
"commit": "<git rev-parse HEAD>",
"files_in_scope": 0, "loc_in_scope": 0, "languages": ["sol"],
"tools_run": {"slither": "ok", "agentic": "ok", "fuzz": "skipped"},
"source": "etherscan|sourcify",
"entrypoints_total": 0, "entrypoints_reviewed": 0,
"entrypoints": [{"file": "src/Vault.sol", "kind": "external-state-changing", "reviewed": true}],
"skipped_paths": [{"path": "lib/", "reason": "third-party dependency"}],
"invariants_modeled": 0,
"verification": {"candidates": 0, "confirmed": 0, "refuted": 0,
"refuted_reasons": [{"file": "src/Vault.sol", "line": 10, "reason": "onlyOwner guard"}]},
"coverage_confidence": "high|partial|low",
"report": "memory/reports/<slug>-${today}.md"
}repo uses the same key as the ledger row above. tools_run mirrors $SCRATCH/sources.txt (slither, agentic, and fuzz when S6.5 ran); source is on-chain only. entrypoints_reviewed/entrypoints_total come from the S5 inventory (the N-cap means these often differ - that is the honest signal), invariants_modeled is the count from S5.0's threat-model.json so the report shows the audit had an explicit spec, and verification is the S6 tally. coverage_confidence: high = every entrypoint reviewed and every applicable tool ran; partial = some entrypoints reviewed: false (incl. past the S5 N budget) or a tool skipped/failed; low = most tools failed or the surface was too large/opaque to review. Never inflate it. Drop a one-line "Scope of review" into the report and any advisory: Reviewed N/M contracts across F .sol files (L LOC); modeled K invariants; tools: slither(<status>), agentic, fuzz(<status>).
Write the human report (§S9.0) too. This manifest is the machine ledger; §S9.0's $WORKDIR/memory/reports/<slug>-${today}.md is its readable professional-audit sibling, written on the same runs (repo / onchain / fixture, clean or finding). Add a "report": "memory/reports/<slug>-${today}.md" field to this manifest so the two cross-link.
The full audit report is the durable artifact, NOT the notification. On every MODE=repo, MODE=onchain, and MODE=fixture run (clean, finding, or skip), write a complete professional-audit-style report to $WORKDIR/memory/reports/<slug>-${today}.md using the fixed template in §S9.0. The slug is the same as the S8 coverage manifest: repo with /->-, onchain-$CHAIN-$ADDR for on-chain, fixture-$FIXTURE for a fixture. This report is the human-readable sibling of the machine manifest (§S8): the manifest is the structured ledger, the report is the readable write-up in the shape of a normal security audit. Build both from the same run data so they never disagree. Also append the terse bullets to memory/logs/ (§Log) that skill-health / vuln-tracker parse. The ./notify body repeats none of it: it is a three-second alert that links to the report.
Write $WORKDIR/memory/reports/<slug>-${today}.md on every run, in the fixed order below so every report reads like a normal security audit and is diffable run-over-run. Fill every section; when one does not apply write N/A and the reason (e.g. Fuzz: N/A (clean audit, hard gate)) rather than dropping the heading. Pull every number from this run's $SCRATCH artifacts (threat-model.json, agentic.json, sources.txt, onchain-context.json) and the S8 manifest. Also record the report path back in the S8 manifest (add "report": "memory/reports/<slug>-${today}.md") so the machine ledger and the human report cross-link.
If this skill runs on a private repo, confirmed-finding detail belongs in this report, but the disclosure-safety rule still holds: describe the flaw, the invariant it breaks, and a prose repro; NEVER paste a drain-ready call sequence or a shrunk fuzz counterexample into the report - that goes only into the operator-gated staged draft (§S7).
# sc-audit report: <target>
- Auditor: aeon sc-audit (autonomous agentic review + Slither + fuzz)
- Date: <today> · Mode: repo | onchain | fixture
- Target: <owner/repo @ <commit>> | <chain>:0x<addr> (<contract name>)
- On-chain context: proxy -> impl <impl addr>; native balance <X> (funds at risk) [MODE=onchain only]
- Outcome: CLEAN (0 confirmed) | <N> confirmed (<a> CRITICAL / <b> HIGH / <c> MEDIUM)
- Disclosure: <channel + staged draft path + operator action, or "none (clean)" / "N/A (fixture)">
## 1. Executive summary
2-4 sentences: what the contract/protocol does, what this run reviewed, the verdict, and the single most important takeaway (the confirmed finding, or the strongest reason the surface is clean).
## 2. Scope
- Contracts reviewed: <N>/<M> (<F> production .sol files, ~<L> LOC)
- Entrypoints reviewed: <reviewed>/<total>
- Commit / address audited: <full sha or checksummed address>
- Not reviewed this run: <the reviewed:false contracts + any subsystem deliberately skipped, with the reason: N-cap, off the value path, dependency lib/>
## 3. Methodology
Threat-model-first: derived <K> invariants (S5.0), hunted a path breaking each (S5), adversarially refuted every survivor (S6), and machine-proved with a fuzzer where a survivor qualified (S6.5).
- Tools: slither(<status>), agentic(<status>), fuzz(<status>)
- Provenance (MODE=onchain): <vendored-file SHA verdicts, e.g. 20/20 IDENTICAL to @uniswap/v4-core 1.0.1; list any DIFFERENT / UNVERIFIED>
- Audit-diff playbook (when the repo ships audits/): audits on file, last audit date, and the genuinely post-audit surface this run concentrated on.
## 4. Threat model and invariants
Actors and trust boundaries: <who can call what; every point where value or authority crosses a boundary>.
| ID | Invariant | Why it matters |
|----|-----------|----------------|
| INV1 | <statement> | <impact if broken> |
## 5. Findings
For each CONFIRMED finding, worst severity first:
### [<SEVERITY>] <one-line title> (<category>)
- Location: <file:line>
- Invariant broken: <INVx>
- Description: what the attacker controls, then what they gain.
- Impact: funds drained / ownership seized / core function bricked; on-chain blast radius if any.
- Reproduction: prose repro + fuzz status ("fuzz counterexample found, held privately in the staged draft" / "fuzzed 25k, no counterexample within budget" / "no fuzz"). No drain-ready sequence here.
- Status: CONFIRMED (survived refutation) | fuzz-proven
- Remediation: the concrete fix.
- Disclosure: <channel>; staged draft: <path if any>.
If 0 confirmed: write "No confirmed findings."
### Candidates raised and refuted
For each candidate that did NOT survive triage/refutation: id, severity-if-it-were-real, category, and the mitigating control that refuted it (the modifier / require / OZ guard / unreachable path). This section is the audit's teeth: it shows what was chased and why it is safe.
## 6. Coverage and limitations
- Explored: the vulnerability classes checked (access control, reentrancy, oracle/price, arithmetic/precision, upgradeability, external-call, signatures/replay, economic/MEV, and the full v4-hook checklist when the target is a hook), the subsystems deep-reviewed, and the Slither hit classes (each with why it is a false positive).
- Not exercised: contracts marked reviewed:false, fuzz coverage gaps / paths the campaign never entered, and `slither=compile-fail` if the build did not succeed.
- Honest partiality: state plainly what a paid human audit would additionally cover.
## 7. Appendix
- Bounty / SECURITY.md intake resolved: <program URL + LIVE? + exact file in scope? or "none found">.
- Machine manifest: `memory/coverage/<slug>-<today>.json`
- Staged disclosure draft: `memory/pending-disclosures/<...>.md` (if any)Always notify with the run outcome: send one alert on every real audit, whether it found something or came back clean. A confirmed finding, a staged disclosure, or an operator-todo (portal) is a warning signal alert (shape below). A clean audit (0 confirmed) still sends an all-clear: line 1 sc-audit - <target>: clean (N files, M invariants), one short line stating what was reviewed plus the Slither/fuzz status, and the subject + artifact links, with no Action: line. MODE=fixture is the only case that notifies nothing (record the result in the run report/log only). This overrides the CLAUDE.md "notify only on signal" default for this skill.
Write a tight body to a scratch file and send it with ./notify -f <file> (keep the -f flag - long argv trips the sandbox). Use exactly this shape - it OVERRIDES any longer one (write plain Markdown; ./notify renders each channel via scripts/notify_format.py, see CLAUDE.md Tools). One shape:
<emoji> sc-audit - <target>: <N sev confirmed> (<class>)
<0-3 short lines: confirmed vs refuted counts + severities; PoC/fuzz status in one clause; on-chain blast radius if MODE=onchain>
Action: <what the operator must do> <- ONLY on a warning signal
link: <subject repo/contract URL> · <artifact blob URL>Action: line: name the operator task (Action: review + file the staged draft, Action: submit to <portal>) or, when disclosure auto-filed/armed itself, Action: none needed - PVR filed. A lower-signal FYI (e.g. only a confirmed low logged, no operator step) is an info/all-clear with no Action line.fuzz counterexample attached / fuzzed 25k, no counterexample within budget / no fuzz); name the disclosure channel and whether it is armed/filed. NO per-finding F1..F4 write-ups, code snippets, gas tables, byte arithmetic, or notes sections - those already live in the artifact.Action: line pointing at the staged draft + resolved channel, and one on-chain-context line (chain, address, proxy/impl, native balance / funds at risk) - that blast radius is the whole reason it is urgent.https://github.com/${GITHUB_REPOSITORY}/blob/main/memory/reports/<slug>-${today}.md (the human report from §S9.0; its machine sibling is memory/coverage/<slug>-${today}.json), or the memory/pending-disclosures/... draft for a staged disclosure.Match soul/ voice if present.
Examples
MODE=repo, confirmed finding, PVR auto-filed:
sc-audit - Acme/vault-core: 1 CRITICAL confirmed (access-control)
Unguarded initialize() lets anyone re-init and seize ownership; INV2 breaks. 1/5 refuted.
Fuzz counterexample attached. Disclosed via GitHub PVR (repo has no SECURITY.md/bounty).
Action: none needed - PVR filed.
link: https://github.com/Acme/vault-core/blob/main/src/Vault.sol · report: https://github.com/${GITHUB_REPOSITORY}/blob/main/memory/reports/Acme-vault-core-2026-08-08.mdMODE=onchain, operator-gated:
sc-audit - base:0x4200...0006: 1 HIGH confirmed (oracle) - staged for operator
Proxy->impl; ~1,240 ETH native balance at risk. INV3 (price integrity) breaks. 2/4 refuted.
Immunefi program resolved as the intake; draft staged, NOT sent.
Action: review + file the staged draft via the Immunefi program.
link: https://basescan.org/address/0x4200000000000000000000000000000000000006 · draft: https://github.com/${GITHUB_REPOSITORY}/blob/main/memory/pending-disclosures/onchain-base-0x4200...0006-<ts>.mdMODE=repo, clean audit (0 confirmed):
sc-audit - Uniswap/UniswapX: clean (14 files, 8 invariants)
0 confirmed, 0 candidates promoted. Slither ok, 5 vendored false positives; no fuzz (no survivors).
link: https://github.com/Uniswap/UniswapX · report: https://github.com/${GITHUB_REPOSITORY}/blob/main/memory/reports/Uniswap-UniswapX-2026-08-26.mdAppend to memory/logs/YYYY-MM-DD.md under ### sc-audit as bullets: target, SOL_FILES/N, slither status, candidate/confirmed counts, disclosure channel, and any operator-todo. This shape is what skill-health parses and what vuln-tracker polls for lifecycle.
slither, solc, solc-select, forge, crytic-compile, echidna, medusa - runs by bare name (the allow-list matches the name, not an absolute path). They are staged in-run best-effort (pip, foundryup, npx); any that fails to stage is skipped by its command -v guard, never fatal. The source pass (S5) is the audit's reliable core and needs no toolchain.api.etherscan.io/v2/api, ~60 chains via chainid=) needs ETHERSCAN_API_KEY - pass it through ./secretcurl as {ETHERSCAN_API_KEY}, never raw; presence-check with ${ETHERSCAN_API_KEY:+x}. Sourcify (sourcify.dev/server) is keyless - plain curl/WebFetch. Blockscout (a per-chain host you add in §S2b) is keyless (v2 REST /api/v2/smart-contracts for source + legacy /api?module=account&action=balance for the native balance) and covers chains that are on neither Etherscan V2 nor Sourcify. An optional BLOCKSCOUT_API_KEY is passed through ./secretcurl as {BLOCKSCOUT_API_KEY} on the legacy balance call. All three are read-only source/metadata reads; no key is ever sent anywhere but Etherscan's own host. When no key is configured the skill still works via Sourcify/Blockscout, at lower verified-source coverage.FOUNDRY_FFI=false (S3) and work only inside the throwaway .scan/<repo> clone. Never run this skill's build/fuzz steps outside the ephemeral CI runner.gh (write mode). Auth'd disclosure emails run in-run via ./secretcurl (see vuln-scanner Arm C, var=disclose) - never on this skill's first pass unless a draft is explicitly armed.End every run with a ## Summary: target audited, contracts reviewed (N/M), tool status, confirmed findings with severity and the invariant each breaks, disclosure channel taken per finding, files/state written (memory/vuln-scanned.json, memory/coverage/..., memory/reports/...), and any operator follow-up (portal submission, maintainer ping).
© aeonfun, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 4 other files (references) in skills/sc-audit of aeonfun/aeon.
Open the folder on GitHubat commit f252074
Sc Audit next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Sc Audit this skillaeonfun/aeon | 767 | — | ~18k | Automated safety check: Pass | MIT | |
| Fizz Convertpashov/skills | 1.2k | 2 repos | ~3.7k | Automated safety check: Pass | MIT | |
| Feynman Auditor0xiehnnkta/nemesis-auditor | 244 | 1 repos | ~11k | Automated safety check: Pass | MIT | |
| Smart Contract Auditgreatpie/smart-contract-audit-skill | 101 | — | ~1.1k | Automated safety check: Pass | None | |
| RadarAuditware/radar | 154 | — | ~2.1k | Automated safety check: Pass | GPL-3.0 | |
| Solidity AuditorGabson0x/bountyforge | 443 | — | ~3.7k | Automated safety check: Pass | None |
pashov/skills
Convert English-language properties in PROPERTIES.md (produced by the Fizz skill) into Solidity assertions inside the existing fuzz harness, then flip their checkboxes.
0xiehnnkta/nemesis-auditor
Deep business logic bug finder using the Feynman technique. An agent skill from 0xiehnnkta/nemesis-auditor.
greatpie/smart-contract-audit-skill
Script-backed, out-of-box auditing workflow for Solidity/EVM repositories based on EVMbench detect/patch/exploit methodology.
Auditware/radar
Use radar for smart contract security analysis, AST generation, and detection template development.
Gabson0x/bountyforge
Security audit of Solidity code while you develop. An agent skill from Gabson0x/bountyforge.
lidofinance/diffyscan
Adds or repairs Diffyscan explorer API routing and response adapters for a new host, chain or payload format.
aeonfun/aeon
Browses open tasks on the TaskMarket agent-worker market and, with explicit operator approval, creates tasks, tracks submissions and submits finished work.
aeonfun/aeon
Sets up and manages an Aeon agent instance that runs skills on a schedule through GitHub Actions: starting, rescheduling, debugging, editing skills and mining chat history.
aeonfun/aeon
Reads a Base Account's address, portfolio and transaction history through the Base MCP server, and stays strictly read-only in unattended Aeon runs, reporting only changes.
aeonfun/aeon
Audits every page of a site each day from its sitemap, scores on-page and technical SEO, checks duplicates across pages and reports what changed since the last run.
aeonfun/aeon
5 concrete real-life actions, leverage-scored against open loops with specificity and anti-fluff gates
aeonfun/aeon
Static linter for an Aeon instance's configuration that catches silent failures such as unquoted schedules, duplicate keys, unconfigured skills and broken MCP references.
Works with
Categories
Deep security audit of a smart-contract repo OR a live on-chain contract by address - detect Solidity, model the protocol invariants and trust boundaries first, run Slither (best-effort) plus a…. Sc Audit is an agent skill from aeonfun/aeon. Deep security audit of a smart-contract repo OR a live on-chain contract by address - detect Solidity, model the protocol invariants and trust boundaries first, run Slither (best-effort) plus a bounded agentic pass that hunts for a path breaking each invariant, triage, adversarially verify, prove with a fuzzer, and drive each finding through the shared responsible-disclosure routing.
Sc Audit fits situations like: tasks that involve Smart contracts.
Run `npx skills add aeonfun/aeon --skill sc-audit -a claude-code`. Or copy the skill folder (skills/sc-audit in aeonfun/aeon) into .claude/skills/sc-audit in your project. Claude Code loads it when a task matches its description.
Run `npx skills add aeonfun/aeon --skill sc-audit -a codex`. Or copy the skill folder (skills/sc-audit in aeonfun/aeon) into .agents/skills/sc-audit in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add aeonfun/aeon --skill sc-audit -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/sc-audit, .gemini/skills/sc-audit, .github/skills/sc-audit and .opencode/skills/sc-audit in your project.
Going by SKILL.md and its folder, Sc Audit needs the command-line tools its instructions call (python3, curl, gh, git, claude and npx) and credentials named ETHERSCAN_API_KEY and BLOCKSCOUT_API_KEY. Our summary lists: A credential in ETHERSCAN_API_KEY; A credential in BLOCKSCOUT_API_KEY.
SKILL.md names 5 domains. In commands or code: github.com, api.etherscan.io, registry.npmjs.org, sourcify.dev and basescan.org; the agent is likely to contact these when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Sc Audit is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 18k tokens (SKILL.md is roughly 70k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 2.6k tokens, read only when the agent opens those files.
Skills that share tags, products or a category with Sc Audit: Fizz Convert (pashov/skills, 1.2k stars), Feynman Auditor (0xiehnnkta/nemesis-auditor, 244 stars), Smart Contract Audit (greatpie/smart-contract-audit-skill, 101 stars) and Radar (Auditware/radar, 154 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
aeonfun (a GitHub organization) maintains it in aeonfun/aeon, which has 767 GitHub stars. The repository holds 82 skills in this directory. The repository was last updated on October 6, 2026.
Source: aeonfun/aeon on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.