AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL unreviewed MIT Self-run

Cosmos SDK + EVM chain audits: see references/cosmos-sdk-chain-audit.md

skill-sekolah76-syadagentic-web3-audit · by Sekolah76

Smart contract security audit — 11 DeFi bug classes (accounting desync, access control, incomplete path, off-by-one, oracle, ERC4626, reentrancy, flash loan, signature replay, proxy, BFT/consensus/liveness), pre-dive kill signals (TVL < $500K etc), Foundry PoC template, grep patterns for each class, and real Immunefi paid examples. Use for any Solidity/Rust contract audit or when deciding whether…

— No reviews yet
0 installs
0 views
— view→install

Install

$ agentstack add skill-sekolah76-syadagentic-web3-audit

Open-source listing, not yet scanned by AgentStack. Follow the source repository for install instructions.

Security review

⚠ Flagged

1 finding(s); flagged for manual review. · v0.1.0 How review works →

  • • Prompt-injection patterns
  • • Secret / credential exfiltration
  • • Dangerous shell & filesystem operations
  • • Untrusted network calls
  • • Known-malicious package signatures
  • high Pipes remote content directly into a shell (remote code execution).

What it can access

  • ● Network access Used
  • ✓ Filesystem access No
  • ✓ Shell / process execution No
  • ✓ Environment & secrets No
  • ✓ Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Reliability & compatibility

— Not yet reviewed
0 installs to date
— no reviews yet
● 1mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Cosmos SDK + EVM chain audits: see references/cosmos-sdk-chain-audit.md? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

WEB3 SMART CONTRACT & BFT CONSENSUS AUDIT

11 bug classes. Pre-dive kill signals. Foundry PoC template. Real paid examples.


PRE-DIVE KILL SIGNALS (check BEFORE any code review)

> ZKsync lesson: $322M TVL + OZ audit + 750K LOC + 5 sessions = 0 findings. Large well-audited bridges are extremely hard.

  1. **TVL 500K LOC → expect 40+ hours for maybe 1 finding. Only proceed if bounty floor > $50K AND you have protocol-specific expertise.

Target scoring (go if >= 6/10):

  • TVL > $10M: +2
  • Immunefi program with Critical >= $50K: +2
  • No top-tier audit on current version: +2
  • | source commit:

Source verification: exact / hypothesis-only (reason) In scope: Trusted/excluded: Known issues avoided: Target invariant:


---

## Scope provenance and regression gate

When the contest's advertised commit is unavailable, record the advertised hash, actual `HEAD`, and remote before analysis. Use mismatched snapshots only to generate hypotheses and validate regressions; do not call a result submission-ready until affected lines are verified against the official scoped artifact. For batched NAV or asynchronous vault findings, test mid-cycle ownership/list/config/cash mutations and both focused and full Foundry suites. See [references/scoped-source-provenance-and-regression.md](references/scoped-source-provenance-and-regression.md).

For pulling verified contract source code without Etherscan API keys, see [references/etherscan-alternatives.md](references/etherscan-alternatives.md).

---

## PRE-AUDIT DUPLICATE CHECK (before writing a single line of code)

> Paraloom lesson: $3K pool, 100+ issues filed in 7 days. Skipping this step = wasting hours on a duplicate.

Always enumerate **existing issues + security log** before starting any code review:

GitHub API — dump ALL issues (open + closed), filter by label/title body

curl -s "https://api.github.com/repos/{org}/{repo}/issues?state=all&per_page=100" | \ jq '.[] | "#\(.number) [\(.state)] \(.title) — \(.user.login)"'


### Kill signals from issue scan
- Already-reported auth bypass → skip anything similar in that code path
- Security log claims "fixed" → verify the fix is actually deployed in the current commit
- Duplicate of an open issue → different root cause? If not, skip
- Same researcher filed 5+ findings in one subsystem → that subsystem is saturated, move to unexplored areas

### Tools for large Rust/Anchor codebases
```bash
# Search patterns across ALL source files
grep -rn "unbounded\|unchecked\|unwrap\|expect\|as u64\|as u32\|as usize" src/ programs/

# Find auth gate mismatches — siblings with different guard levels
grep -rn "node_info.node_type" src/ | sort

# Find unbounded collections (memory-exhaustion candidates)
grep -rn "Vec "Read ALL sibling functions. If `vote()` has a modifier, check `poke()`, `reset()`, `harvest()`. The missing modifier on the sibling IS the bug."

This single rule explains 19% of all Critical findings.

---

## 1. ACCOUNTING STATE DESYNCHRONIZATION
> #1 Critical bug class — 28% of all Criticals on Immunefi.

### What It Is
Two state variables supposed to stay in sync. One code path updates A but forgets B. Later code reads both and makes decisions based on stale B.

Real Value = A - B If A updated but B isn't → Real Value appears larger → phantom value


### Root Cause Patterns

**Variant 1: Phantom Yield** (Yeet protocol — 35 duplicate reports)
```solidity
function startUnstake(uint256 amount) external {
    totalSupply -= amount;  // decremented BEFORE transfer
    // aToken.balanceOf(this) still reflects old value
    // yieldAmount = aToken.balanceOf - totalSupply = phantom yield
}

Variant 2: Fast Path Skips State Update (Alchemix V3)

function claimRedemption(uint256 tokenId) external {
    if (transmuter.balance >= amount) {
        transmuter.transfer(user, amount);
        _burn(tokenId);
        return;  // EARLY RETURN — cumulativeEarmarked, _redemptionWeight, totalDebt never updated
    }
    // Slow path: updates all state vars correctly
    alchemist.redeem(...);
}

Variant 3: Update Happens in Wrong Order (Alchemix)

function deposit(uint256 amount) external {
    _shares = (amount * totalShares) / totalAssets;  // calculated BEFORE deposit
    totalAssets += amount;   // assets added AFTER shares calculated → wrong rate
}

Grep Patterns

# Find all accounting variables
grep -rn "totalSupply\|totalShares\|totalAssets\|totalDebt\|cumulativeReward\|rewardPerShare" contracts/

# Find all early returns in claim/redeem functions
grep -rn "\breturn\b" contracts/ -B3 | grep -B3 "if\b"

# For each early return: which state updates in normal path are skipped?

2. ACCESS CONTROL

> #2 Critical — 19% of Criticals. $953M lost in 2024 alone.

Variant 1: Missing Modifier on Sibling Function

function vote(uint256 tokenId) external onlyNewEpoch(tokenId) {  // guarded
function reset(uint256 tokenId) external onlyNewEpoch(tokenId) { // guarded
function poke(uint256 tokenId) external {                         // NO GUARD → infinite FLUX inflation
}

Variant 2: Wrong Check (Existence vs Ownership)

function split(uint256 tokenId, uint256 amount) external {
    _requireOwned(tokenId);  // checks if token EXISTS, not if caller OWNS it
    _burn(tokenId);
    _mint(msg.sender, amount);  // attacker steals tokens they don't own
}

Variant 3: Silent Modifier (if vs require)

// VULNERABLE — non-admin silently gets through:
modifier onlyAdmin() {
    if (msg.sender == admin) {
        _;  // body only executes for admin, but non-admin doesn't revert
    }
}
// CORRECT: require(msg.sender == admin, "Not admin"); _;

Variant 4: Uninitialized Proxy

function initialize(address _owner) public {  // MISSING: initializer modifier
    owner = _owner;  // anyone can call → become owner
}
// Fix: constructor() { _disableInitializers(); }

Variant 5: Two-Transaction Deployment Frontrunning

When an L2/custom chain forces msg.sender = address(0) during ContractCreate (e.g., Goliath/Onyx Mainnet relay), the deployer must call init() in a separate transaction. This creates a frontrunning window: any mempool watcher can call init() first and hijack ownership.

/// @dev "Deploy this contract, then call init() in a separate tx."
///      msg.sender=address(0) in ContractCreate → can't set owner in constructor.
constructor() {}  // empty — no _disableInitializers either

function init(address owner_, ...) external initializer {
    __Ownable_init(owner_);  // anyone can call → set themselves as owner
}

Kill signal: constructor is empty AND init() has no caller restriction AND code comments mention "separate tx" or "msg.sender=address(0)".

Fix patterns:

  • Factory contract that deploys + initializes atomically in one tx
  • Pass deployer address as constructor arg and require msg.sender == _deployer in init()
  • Use tx.origin guard in constructor (less ideal but workable)
  • Use CREATE2 with initialization calldata bundled

Severity: Medium if contract has no funds at deploy time (deployer can just redeploy). High if funds/integrations proceed before ownership is verified.

Grep Patterns

# Find sibling function families — do ALL have the same modifier set?
grep -rn "function vote\|function poke\|function reset\|function update\|function claim\|function harvest" contracts/ -A2

# Ownership check: existence vs ownership?
grep -rn "_requireOwned\|ownerOf\|_isApprovedOrOwner\|_checkAuthorized" contracts/ -B5

# Silent modifiers
grep -rn "modifier\b" contracts/ -A8 | grep -B3 "if (" | grep -v "require\|revert"

# Uninitialized initializer
grep -rn "function initialize\b" contracts/ -A3
grep -rn "_disableInitializers()" contracts/

Real Paid Examples

| Protocol | Payout | Bug | |---|---|---| | Wormhole | $10M | Uninitialized UUPS proxy → anyone calls initialize() | | ZeroLend | n/a | split() uses existence check, not ownership check | | Alchemix | n/a | poke() missing onlyNewEpoch → infinite FLUX inflation | | Parity | $150M frozen | No access control on initWallet() in library |


3. INCOMPLETE CODE PATH

> #3 Critical — 17% of Criticals.

The Function Family Comparison Test

1. List all state changes in function A (deposit/place/create)
2. List all state changes in function B (withdraw/update/cancel)
3. For each state change in A: does B have the corresponding reverse?
4. For each token transfer in A: does B have the corresponding refund?
If A does X but B doesn't do the reverse of X → BUG.

Variant 1: Update Function Missing Refund (ThunderNFT)

function place_order(OrderInput calldata order) external {
    token.safeTransferFrom(msg.sender, address(this), order.price);  // takes tokens
    orders[orderId] = order;
}
function update_order(OrderInput calldata updatedOrder) external {
    // BUG: NO REFUND for sell orders when price decreases → tokens permanently stuck
    orders[orderId] = updatedOrder;
}

Variant 2: Partial Fill Token Stuck (Plume)

function swapForETH(uint256 amountIn) external {
    token.safeTransferFrom(msg.sender, address(this), amountIn);
    uint256 filled = dex.swap(amountIn);  // partial fill possible
    _refundExcessEth(amountIn - filled);  // BUG: refunds ETH only, not ERC20
}

Variant 3: mint() Bypasses Check That deposit() Has (MetaPool)

function deposit(uint256 assets, address receiver) public override {
    shares = _deposit(assets, receiver);  // includes receipt validation
}
function mint(uint256 shares, address receiver) public override {
    assets = convertToAssets(shares);
    _mint(receiver, shares);  // MISSING: _deposit() validation → mints without receiving assets
}

Grep Patterns

grep -rn "function place_\|function create_\|function add_\|function open_" contracts/ -A5
grep -rn "function update_\|function modify_\|function cancel_" contracts/ -A5
grep -rn "safeApprove\b" contracts/    # safeApprove without zero-reset before
grep -rn "delete\b" contracts/ -B5 -A5  # delete before operation completes
grep -rn "function deposit\|function mint\|function withdraw\|function redeem" contracts/ -A10

4. OFF-BY-ONE & BOUNDARY CONDITIONS

> #4 High — 22% of Highs. Single character change. Massive impact.

Root Cause

// VeChain Stargate — post-exit reward drain:
function _claimableDelegationPeriods(address delegator) internal view returns (uint256) {
    if (endPeriod > nextClaimablePeriod) {  // BUG: should be >=
        return 0;  // exited users get nothing
    }
    return nextClaimablePeriod - lastClaimedPeriod;  // rewards for period AFTER exit
}

Mental Test for Every Comparison

> For every if (A > B): "What happens when A == B?" Is that correct?

6 Boundary Locations to Check

  1. Period/Epoch boundaries: > vs >= at period end
  2. Time-based locks: does block.timestamp == deadline lock or unlock?
  3. Loop break conditions: break with > vs >=
  4. Array index boundaries: i = amount allows exact full withdrawal?
  5. Rounding/precision: can any input produce 0 output that should be non-zero?

Grep Patterns

# Boundaries in comparisons
grep -rn "Period\|Epoch\|Round\|Deadline\|period\|epoch\|deadline" contracts/ -A3 | grep "[<>][^=]"

# Loop breaks
grep -rn "\bbreak\b" contracts/ -B10

# Off-by-one in array access
grep -rn "\.length\s*-\s*1\|i\s* 12% of all reports. Largest individual payouts. $117M Mango, $70M Curve.

### Bug A: Missing Staleness Check (most common)
```solidity
// VULNERABLE:
(, int256 price,,,) = priceFeed.latestRoundData();
return uint256(price);  // If Chainlink node goes down, stale price returned indefinitely

// CORRECT:
(, int256 price,, uint256 updatedAt,) = priceFeed.latestRoundData();
require(block.timestamp - updatedAt  0, "Invalid price");

Bug B: Missing Confidence Interval (Pyth)

// VULNERABLE:
PythStructs.Price memory p = pyth.getPriceUnsafe(priceFeed);
return p.price;  // ignores p.conf (confidence interval)

// CORRECT:
require(p.conf * 10  10% of price = untrustworthy

Bug C: TWAP Too Short (flash loan manipulatable)

// VULNERABLE: 60-second TWAP
uint32[] memory secondsAgos = new uint32[](2);
secondsAgos[0] = 60; secondsAgos[1] = 0;
// Flash loan can shift price for entire 60s window

// CORRECT: 1800s minimum TWAP (30 min)

Bug D: Single-Source Oracle

// VULNERABLE: only Uniswap spot price
uint price = getUniswapSpotPrice(token);  // flash loan manipulatable

// CORRECT: Chainlink primary, Uniswap TWAP as fallback, require close agreement

Grep Patterns

# Missing staleness check
grep -rn "latestRoundData" contracts/ -A5 | grep -v "updatedAt\|timestamp"

# Pyth price usage — confidence interval checked?
grep -rn "getPriceUnsafe\|getPrice\b" contracts/ -A8 | grep -v "conf\|confidence"

# TWAP windows — short TWAP flag
grep -rn "secondsAgo\|TWAP\|cardinality" contracts/ -A5

6. ERC4626 VAULT ATTACKS

Exchange Rate Manipulation (near-empty vault)

// VULNERABLE — first depositor attack:
// 1. Attacker deposits 1 wei → gets 1 share
// 2. Attacker donates large amount directly (transfer, not deposit)
// 3. Exchange rate: 1 share = (1 + donation) assets
// 4. Victim deposits → rounds down to 0 shares → free donation to attacker

// CORRECT: virtual shares (OpenZeppelin v4.9+)
function _decimalsOffset() internal view virtual override returns (uint8) {
    return 9;  // add 1e9 virtual shares + assets to prevent manipulation
}

ERC4626 Transfer (moves shares but not stake/lock records)

// VULNERABLE: shares transferred, but lock records stay with original owner
// → shares stuck, can't redeem → permanent freeze (Belong pattern)
function transfer(address to, uint256 amount) external override {
    _transfer(msg.sender, to, amount);  // moves shares
    // MISSING: transfer lock record from msg.sender to `to`
}

Grep Patterns

grep -rn "function transfer\|function transferFrom" contracts/ -A15
grep -rn "function deposit\|function mint\|function withdraw\|function redeem" contracts/ -A10

7. REENTRANCY

> 2016–present. CEI pattern prevents it. Still found in DeFi.

Variants

  • Single-function: attacker re-enters same function before state updated
  • Cross-function: re-enters a sibling function with stale state
  • Cross-contract: re-enters via a callback to another protocol
  • Read-only: re-enters a view function that returns stale data used by attacker

Root Cause Pattern

// VULNERABLE (effects after interaction):
function withdraw(uint256 amount) external {
    require(balances[msg.sender] >= amount);
    (bool success,) = msg.sender.call{value: amount}("");  // INTERACTION first
    require(success);
    balances[msg.sender] -= amount;  // EFFECT after → reentrancy window
}

// CORRECT (CEI — Checks, Effects, Interactions):
function withdraw(uint256 amount) external {
    require(balances[msg.sender] >= amount);  // CHECK
    balances[msg.sender] -= amount;            // EFFECT
    (bool success,) = msg.sender.call{value: amount}("");  // INTERACTION last
    require(success);
}

Grep Patterns

# External calls before state updates
grep -rn "\.call{value\|safeTransfer\|transfer(" contracts/ -B10 | grep -v "require\|revert"

# Missing nonReentrant modifier on critical functions
grep -rn "function withdraw\|function redeem\|function claim" contracts/ -A2 | grep -v "nonReentrant"

# Storage slot for reentrancy guard
grep -rn "nonReentrant\|ReentrancyGuard\|_notEntered" contracts/

8. FLASH LOAN ATTACKS

> Flash loan is an amplifier, not a root bug. Bug = protocol trusts same-tx > manipulable state (spot price, raw balanceOf, live votes, mid-callback views).

Mental model

borrow →

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Sekolah76](https://github.com/Sekolah76)
- **Source:** [Sekolah76/syadagentic](https://github.com/Sekolah76/syadagentic)
- **License:** MIT
- **Homepage:** https://github.com/Sekolah76/syadagentic

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.