skills/blockchain-auditor/SKILL.md
Security audit smart contracts for exploitable vulnerabilities from an unprivileged context. Use when analyzing Solidity contracts, reviewing bytecode, testing exploits on forks, or searching for ways to extract funds without owner access. Covers verified and unverified contracts, bytecode disassembly, vulnerability ranking, proof-of-concept exploit generation, DeFi protocol attacks, and cross-chain bridge security.
npx skillsauth add ckorhonen/claude-skills blockchain-auditorInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Systematically audit smart contracts to identify exploitable vulnerabilities that allow an unprivileged account to extract funds or gain unauthorized access.
# For verified contracts (Etherscan source available)
cast etherscan-source <address> --chain mainnet
# For unverified contracts (bytecode only)
cast code <address> --rpc-url $ETH_RPC_URL
cast disassemble <bytecode>
# Fork testing
forge test --fork-url $ETH_RPC_URL -vvv
# Static analysis
slither . --checklist
Gather contract information:
# Check balance
cast balance <address> --rpc-url $ETH_RPC_URL
# Get bytecode
cast code <address> --rpc-url $ETH_RPC_URL
# Check if verified on Etherscan
curl "https://api.etherscan.io/api?module=contract&action=getsourcecode&address=<address>&apikey=$ETHERSCAN_API_KEY"
# Check for proxy implementation
cast storage <address> 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc --rpc-url $ETH_RPC_URL
Identify contract type:
Identify high-risk functions:
selfdestruct / suicide - can drain all ETHdelegatecall - can execute arbitrary codecall with user-controlled data - arbitrary callstransfer / send without checks - reentrancywithdraw / claim / redeem - fund extraction pointsinitialize / init - proxy initialization (can it be called twice?)permit / permitAll - off-chain approval bypassflash / flashLoan - flash loan entry pointsCheck access control patterns:
// Weak patterns to look for:
require(tx.origin == owner); // tx.origin bypass
require(msg.sender == owner); // Check if owner is compromised
// Missing modifier on sensitive function
function withdraw() public { // No onlyOwner!
payable(msg.sender).transfer(address(this).balance);
}
// Proxy: unprotected initialize
function initialize(address _owner) public { // Missing initializer modifier!
owner = _owner;
}
Known vulnerability patterns:
For DeFi protocols, these vectors are highest-value and commonly exploitable:
Price Oracle Manipulation:
// Vulnerable: spot price used as oracle
function getPrice() public view returns (uint256) {
(uint112 reserve0, uint112 reserve1,) = IUniswapV2Pair(pool).getReserves();
return reserve1 * 1e18 / reserve0; // Manipulable in a single tx!
}
// Attack: flash loan → manipulate pool → call vulnerable function → repay
getReserves(), slot0(), direct AMM pool queriesFlash Loan Attack Surface:
Reentrancy Variants:
// Cross-function reentrancy
function withdraw() external {
// State updated after transfer → reenter via deposit()
IERC20(token).safeTransfer(msg.sender, amounts[msg.sender]);
amounts[msg.sender] = 0; // Too late!
}
// Read-only reentrancy (in Curve, Balancer)
// Attack: during callback, totalSupply() returns old value
// Another protocol reads it via getVirtualPrice() during the callback
ERC-4626 Vault Inflation Attack:
(deposit * totalShares) / totalAssetstotalShares = 0, first depositor gets deposit shares; then donate to inflateSignature/Permit Vulnerabilities:
// EIP-2612 permit can be front-run
// Front-runner takes the permit, uses it themselves before victim's tx
// Safe if permit is used atomically in the same tx
// Check: does permit use chainId? (cross-chain replay)
// Check: does it use nonces? (replay protection)
Governance/Timelock Attacks:
Disassemble bytecode:
cast disassemble <bytecode> > contract.asm
Extract function selectors:
# Look for PUSH4 followed by 4 bytes
grep -oE '63[0-9a-f]{8}' contract.asm | cut -c3-10 | sort -u
Look up signatures:
curl "https://www.4byte.directory/api/v1/signatures/?hex_signature=0x<selector>"
# Also try openchain.xyz/signature-database
Identify dangerous opcodes:
| Opcode | Hex | Risk |
|--------|-----|------|
| SELFDESTRUCT | ff | Critical - destroys contract |
| DELEGATECALL | f4 | High - arbitrary code execution |
| CALL | f1 | Medium - external calls |
| CALLCODE | f2 | High - deprecated, dangerous |
| CREATE2 | f5 | Medium - deterministic deployment |
| TLOAD/TSTORE | 5c/5d | New in EIP-1153 - transient storage |
Analyze control flow:
Rate each finding by exploitation likelihood given current blockchain state:
| Rating | Criteria | Action | |--------|----------|--------| | Critical | Directly exploitable now, high value | Immediate PoC | | High | Exploitable with specific conditions met | Fork test | | Medium | Requires unlikely conditions | Document | | Low | Theoretical, conditions very unlikely | Note only |
Factors affecting likelihood:
Set up Foundry test:
// test/Exploit.t.sol
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "forge-std/interfaces/IERC20.sol";
interface IFlashLoanProvider {
function flashLoan(uint256 amount) external;
}
contract ExploitTest is Test {
address target = 0x<TARGET_ADDRESS>;
address attacker;
function setUp() public {
// Fork at specific block for determinism
vm.createSelectFork(vm.envString("ETH_RPC_URL"), <BLOCK_NUMBER>);
attacker = makeAddr("attacker");
vm.deal(attacker, 1 ether);
}
function test_exploit() public {
uint256 balanceBefore = attacker.balance;
vm.prank(attacker);
(bool success,) = target.call(abi.encodeWithSelector(0x<SELECTOR>));
uint256 balanceAfter = attacker.balance;
// CRITICAL: Verify actual fund extraction
assertGt(balanceAfter, balanceBefore, "Exploit failed - no funds extracted");
}
// Flash loan exploit template
function test_flashLoanExploit() public {
uint256 targetBalanceBefore = IERC20(token).balanceOf(target);
vm.prank(attacker);
FlashLoanAttacker exploitContract = new FlashLoanAttacker(target);
exploitContract.attack();
assertLt(IERC20(token).balanceOf(target), targetBalanceBefore, "No drain");
assertGt(IERC20(token).balanceOf(attacker), 0, "No profit");
}
}
Run on fork:
forge test --fork-url $ETH_RPC_URL -vvvv --match-test "test_exploit"
Verify fund extraction (not just call success):
For confirmed vulnerabilities, create a report:
# Vulnerability Report: [Contract Address]
## Summary
- **Severity**: Critical/High/Medium/Low
- **Type**: [Reentrancy/Access Control/Oracle Manipulation/etc.]
- **Impact**: [Amount at risk, what attacker gains]
- **Exploitable**: Yes/No (with current blockchain state)
- **Block confirmed**: [Block number of fork test]
## Vulnerable Function
\`\`\`solidity
function vulnerableFunction() public {
// Vulnerable code
}
\`\`\`
## Attack Vector
1. Attacker calls function X with parameter Y
2. Contract fails to check Z
3. Funds transferred to attacker
## Proof of Concept
\`\`\`solidity
// Foundry test that demonstrates the exploit
function test_exploit() public {
// Setup and exploit code
}
\`\`\`
## Execution Script (if confirmed)
\`\`\`bash
cast send <target> "vulnerableFunction()" --rpc-url $ETH_RPC_URL --private-key $PRIVATE_KEY
\`\`\`
## Remediation
- Add access control modifier
- Implement checks-effects-interactions pattern
- Use SafeMath for arithmetic (or upgrade to Solidity >= 0.8.0)
- Replace spot price with TWAP oracle
| Version | Issue | Check |
|---------|-------|-------|
| < 0.8.0 | Integer overflow/underflow | SafeMath usage |
| < 0.6.0 | Constructor name confusion | constructor() keyword |
| < 0.5.0 | Uninitialized storage pointers | Variable declarations |
| Any | tx.origin authentication | Access control patterns |
| Any | Proxy storage collision | EIP-1967 slots used |
Be aware of patterns that look exploitable but aren't:
kill() succeeds but requires N-of-M confirmationswithdraw() only returns caller's deposited amountALWAYS verify actual fund movement on fork before concluding exploitability.
| Tool | Purpose | Command |
|------|---------|---------|
| cast | RPC calls, disassembly | cast code/call/send/disassemble |
| forge | Fork testing | forge test --fork-url |
| anvil | Local fork node | anvil --fork-url $RPC |
| slither | Static analysis | slither . --checklist |
| 4byte.directory | Selector lookup | API or web |
| openchain.xyz | Selector lookup | API or web |
| Etherscan | Source code, ABI | API or web |
| Mythril | Symbolic execution | myth analyze |
| Tenderly | Transaction simulation | Web UI |
| Dedaub | Decompiler for bytecode | dedaub.com |
Required environment variables:
export ETH_RPC_URL="https://eth-mainnet.g.alchemy.com/v2/<KEY>"
export ETHERSCAN_API_KEY="<KEY>"
Required tools:
# Foundry (forge, cast, anvil)
curl -L https://foundry.paradigm.xyz | bash
foundryup
# Slither
pip3 install slither-analyzer
# Node.js (for Hardhat if needed)
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox
# 1. Check contract value and code
cast balance 0x<address> --rpc-url $ETH_RPC_URL
cast code 0x<address> --rpc-url $ETH_RPC_URL > bytecode.hex
# 2. Check for proxy (EIP-1967 slot)
cast storage 0x<address> 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc --rpc-url $ETH_RPC_URL
# 3. Disassemble if unverified
cast disassemble $(cat bytecode.hex) > contract.asm
# 4. Extract and lookup selectors
grep -oE '63[0-9a-f]{8}' contract.asm | cut -c3-10 | sort -u | while read sel; do
sig=$(curl -s "https://www.4byte.directory/api/v1/signatures/?hex_signature=0x$sel" | jq -r '.results[0].text_signature // "unknown"')
echo "0x$sel: $sig"
done
# 5. Run slither on source (if available)
slither . --checklist 2>&1 | head -100
# 6. Create and run fork test
forge test --fork-url $ETH_RPC_URL -vvvv
# 7. Verify fund extraction (not just call success!)
# 8. Generate report if confirmed exploitable
When auditing multiple contracts, track findings in SQLite:
CREATE TABLE contracts (
address TEXT PRIMARY KEY,
balance_usd REAL,
is_verified INTEGER,
exploitable INTEGER DEFAULT 0,
attack_type TEXT,
notes TEXT
);
-- Update after analysis
UPDATE contracts SET
exploitable = 0,
notes = 'Multi-sig wallet. kill() requires confirmations. NOT exploitable.'
WHERE address = '0x...';
documentation
Create or expand an Idea.md / IDEA.md file from a rough description, existing repo, conversation history, notes, or other early-stage product inputs. Use when the user asks to "write an Idea.md", "turn this into an idea file", "capture this product idea", "expand this concept", or wants a repo-grounded concept brief before validation, PRD, or implementation work.
development
Write structured implementation plans from specs or requirements before touching code. Use when given a spec, requirements doc, or feature description, when user says "plan this out", "write a plan for", "how should we implement", or before starting any multi-step coding task.
testing
Expert guidance for video editing with ffmpeg, encoding best practices, and quality optimization. Use when working with video files, transcoding, remuxing, encoding settings, color spaces, or troubleshooting video quality issues.
development
Opinionated constraints for building better interfaces with agents. Use when building UI components, implementing animations, designing layouts, reviewing frontend accessibility, or working with Tailwind CSS, motion/react, or accessible primitives like Radix/Base UI.