MCPInstall

security

Solidity security patterns, common vulnerabilities, and pre-deploy audit checklist. The specific code patterns that prevent real losses — not just warnings, but defensive implementations. Use before deploying any contract, when reviewing code, or when building anything that holds or moves value.

Installs
40
Last 24h
—
Trending rank
—
Security
Not audited

Install

Works with every agent the skills CLI supports.

npx skills add https://github.com/austintgriffith/ethskills --skill security
  1. 01

    Install

    Run the command in your project. The CLI asks which agents to install for.

  2. 02

    Describe the task

    Ask your agent for something this skill covers. It loads security automatically when relevant.

  3. 03

    Review the result

    The agent follows the SKILL.md steps, scripts and references bundled with the skill.

SKILL.md

The instructions your agent reads when the skill is triggered.

Smart Contract Security

What You Probably Got Wrong

"Solidity 0.8+ prevents overflows, so I'm safe." Overflow is one of dozens of attack vectors. The big ones today: reentrancy, oracle manipulation, approval exploits, and decimal mishandling.

"I tested it and it works." Working correctly is not the same as being secure. Most exploits call functions in orders or with values the developer never considered.

"It's a small contract, it doesn't need an audit." The DAO hack was a simple reentrancy bug. The Euler exploit was a single missing check. Size doesn't correlate with safety.

Critical Vulnerabilities (With Defensive Code)

1. Token Decimals Vary

USDC has 6 decimals, not 18. This is the #1 source of "where did my money go?" bugs.

// ❌ WRONG — assumes 18 decimals. Transfers 1 TRILLION USDC.
uint256 oneToken = 1e18;

// ✅ CORRECT — check decimals
uint256 oneToken = 10 ** IERC20Metadata(token).decimals();

Common decimals:

TokenDecimals
USDC, USDT6
WBTC8
DAI, WETH, most tokens18

When doing math across tokens with different decimals, normalize first:

// Converting USDC amount to 18-decimal internal accounting
uint256 normalized = usdcAmount * 1e12; // 6 + 12 = 18 decimals

2. No Floating Point in Solidity

Solidity has no float or double. Division truncates to zero.

// ❌ WRONG — this equals 0
uint256 fivePercent = 5 / 100;

// ✅ CORRECT — basis points (1 bp = 0.01%)
uint256 FEE_BPS = 500; // 5% = 500 basis points
uint256 fee = (amount * FEE_BPS) / 10_000;

Always multiply before dividing. Division first = precision loss.

// ❌ WRONG — loses precision
uint256 result = a / b * c;

// ✅ CORRECT — multiply first
uint256 result = (a * c) / b;

For complex math, use fixed-point libraries like PRBMath or ABDKMath64x64.

3. Reentrancy

An external call can call back into your contract before the first call finishes. If you update state AFTER the external call, the attacker re-enters with stale state.

// ❌ VULNERABLE — state updated after external call
function withdraw() external {
    uint256 bal = balances[msg.sender];
    (bool success,) = msg.sender.call{value: bal}(""); // ← attacker re-enters here
    require(success);
    balances[msg.sender] = 0; // Too late — attacker already withdrew again
}

// ✅ SAFE — Checks-Effects-Interactions pattern + reentrancy guard
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

function withdraw() external nonReentrant {
    uint256 bal = balances[msg.sender];
    require(bal > 0, "Nothing to withdraw");
    
    balances[msg.sender] = 0;  // Effect BEFORE interaction
    
    (bool success,) = msg.sender.call{value: bal}("");
    require(success, "Transfer failed");
}

The pattern: Checks → Effects → Interactions (CEI)

  1. Checks — validate inputs and conditions
  2. Effects — update all state
  3. Interactions — external calls last

Always use OpenZeppelin's ReentrancyGuard as a safety net on top of CEI.

4. SafeERC20

Some tokens (notably USDT) don't return bool on transfer() and approve(). Standard calls will revert even on success.

// ❌ WRONG — breaks with USDT and other non-standard tokens
token.transfer(to, amount);
token.approve(spender, amount);

// ✅ CORRECT — handles all token implementations
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
using SafeERC20 for IERC20;

token.safeTransfer(to, amount);
token.safeApprove(spender, amount);

Other token quirks to watch for:

  • Fee-on-transfer tokens: Amount received < amount sent. Always check balance before and after.
  • Rebasing tokens (stETH): Balance changes without transfers. Use wrapped versions (wstETH).
  • Pausable tokens (USDC): Transfers can revert if the token is paused.
  • Blocklist tokens (USDC, USDT): Specific addresses can be blocked from transacting.

5. Never Use DEX Spot Prices as Oracles

A flash loan can manipulate any pool's spot price within a single transaction. This has caused hundreds of millions in losses.

// ❌ DANGEROUS — manipulable in one transaction
function getPrice() internal view returns (uint256) {
    (uint112 reserve0, uint112 reserve1,) = uniswapPair.getReserves();
    return (reserve1 * 1e18) / reserve0; // Spot price — easily manipulated
}

// ✅ SAFE — Chainlink with staleness + sanity checks
function getPrice() internal view returns (uint256) {
    (, int256 price,, uint256 updatedAt,) = priceFeed.latestRoundData();
    require(block.timestamp - updatedAt < 3600, "Stale price");
    require(price > 0, "Invalid price");
    return uint256(price);
}

If you must use onchain price data:

  • Use TWAP (Time-Weighted Average Price) over 30+ minutes — resistant to single-block manipulation
  • Uniswap V3 has built-in TWAP oracles via observe() (verified addresses: addresses/SKILL.md)
  • Still less safe than Chainlink for high-value decisions

6. Vault Inflation Attack

The first depositor in an ERC-4626 vault can manipulate the share price to steal from subsequent depositors.

The attack:

  1. Attacker deposits 1 wei → gets 1 share
  2. Attacker donates 1000 tokens directly to the vault (not via deposit)
  3. Now 1 share = 1001 tokens
  4. Victim deposits 1999 tokens → gets 1999 * 1 / 2000 = 0 shares (rounds down)
  5. Attacker redeems 1 share → gets all 3000 tokens

The fix — virtual offset:

function convertToShares(uint256 assets) public view returns (uint256) {
    return assets.mulDiv(
        totalSupply() + 1e3,    // Virtual shares
        totalAssets() + 1        // Virtual assets
    );
}

The virtual offset makes the attack uneconomical — the attacker would need to donate enormous amounts to manipulate the ratio.

OpenZeppelin's ERC4626 implementation includes this mitigation by default since v5.

7. Infinite Approvals

Never use type(uint256).max as approval amount.

// ❌ DANGEROUS — if this contract is exploited, attacker drains your entire balance
token.approve(someContract, type(uint256).max);

// ✅ SAFE — approve only what's needed
token.approve(someContract, exactAmountNeeded);

// ✅ ACCEPTABLE — approve a small multiple for repeated interactions
token.approve(someContract, amountPerTx * 5); // 5 transactions worth

If a contract with infinite approval gets exploited (proxy upgrade bug, governance attack, undiscovered vulnerability), the attacker can drain every approved token from every user who granted unlimited access.

8. Access Control

Every state-changing function needs explicit access control. "Who should be able to call this?" is the first question.

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

// ❌ WRONG — anyone can drain the contract
function emergencyWithdraw() external {
    token.transfer(msg.sender, token.balanceOf(address(this)));
}

// ✅ CORRECT — only owner
function emergencyWithdraw() external onlyOwner {
    token.transfer(owner(), token.balanceOf(address(this)));
}

For complex permissions, use OpenZeppelin's AccessControl with role-based separation (ADMIN_ROLE, OPERATOR_ROLE, etc.).

9. Input Validation

Never trust inputs. Validate everything.

function deposit(uint256 amount, address recipient) external {
    require(amount > 0, "Zero amount");
    require(recipient != address(0), "Zero address");
    require(amount <= maxDeposit, "Exceeds max");
    
    // Now proceed
}

Common missed validations:

  • Zero addresses (tokens sent to 0x0 are burned forever)
  • Zero amounts (wastes gas, can cause division by zero)
  • Array length mismatches in batch operations
  • Duplicate entries in arrays
  • Values exceeding reasonable bounds

MEV & Sandwich Attacks

MEV (Maximal Extractable Value): Validators and searchers can reorder, insert, or censor transactions within a block. They profit by frontrunning your transaction, backrunning it, or both.

Sandwich Attacks

The most common MEV attack on DeFi users:

1. You submit: swap 10 ETH → USDC on Uniswap (slippage 1%)
2. Attacker sees your tx in the mempool
3. Attacker frontruns: buys USDC before you → price rises
4. Your swap executes at a worse price (but within your 1% slippage)
5. Attacker backruns: sells USDC after you → profits from the price difference
6. You got fewer USDC than the true market price

Protection

// ✅ Set explicit minimum output — don't set amountOutMinimum to 0
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter
    .ExactInputSingleParams({
        tokenIn: WETH,
        tokenOut: USDC,
        fee: 3000,
        recipient: msg.sender,
        amountIn: 1 ether,
        amountOutMinimum: 1900e6, // ← Minimum acceptable USDC (protects against sandwich)
        sqrtPriceLimitX96: 0
    });

For users/frontends:

  • Use Flashbots Protect RPC (https://rpc.flashbots.net) — sends transactions to a private mempool, invisible to sandwich bots
  • Set tight slippage limits (0.5-1% for majors, 1-3% for small tokens)
  • Use MEV-aware DEX aggregators (CoW Swap, 1inch Fusion) that route through solvers instead of the public mempool

When MEV matters:

  • Any swap on a DEX (especially large swaps)
  • Any large DeFi transaction (deposits, withdrawals, liquidations)
  • NFT mints with high demand (bots frontrun to mint first)

When MEV doesn't matter:

  • Simple ETH/token transfers
  • L2 transactions (sequencers process transactions in order — no public mempool reordering)
  • Private mempool transactions (Flashbots, MEV Blocker)

Proxy Patterns & Upgradeability

Smart contracts are immutable by default. Proxies let you upgrade the logic while keeping the same address and state.

When to Use Proxies

  • Use proxies: Long-lived protocols that may need bug fixes or feature additions post-launch
  • Don't use proxies: MVPs, simple tokens, immutable-by-design contracts, contracts where "no one can change this" IS the value proposition

Proxies add complexity, attack surface, and trust assumptions. Users must trust that the admin won't upgrade to a malicious implementation. Don't use proxies just because you can.

UUPS vs Transparent Proxy

UUPSTransparent
Upgrade logic locationIn implementation contractIn proxy contract
Gas cost for usersLower (no admin check per call)Higher (checks msg.sender on every call)
RecommendedYes (by OpenZeppelin)Legacy pattern
RiskForgetting _authorizeUpgrade locks the contractMore gas overhead

Use UUPS. It's cheaper, simpler, and what OpenZeppelin recommends.

UUPS Implementation

// Implementation contract (the logic)
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

contract MyContractV1 is Initializable, UUPSUpgradeable, OwnableUpgradeable {
    uint256 public value;

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers(); // Prevent implementation from being initialized
    }

    function initialize(address owner) public initializer {
        __Ownable_init(owner);
        __UUPSUpgradeable_init();
        value = 42;
    }

    function _authorizeUpgrade(address) internal override onlyOwner {}
}

Critical Rules

  1. Use initializer instead of constructor — proxies don't run constructors
  2. Never change storage layout — only append new variables at the end, never delete or reorder
  3. Use OpenZeppelin's upgradeable contracts — @openzeppelin/contracts-upgradeable, not @openzeppelin/contracts
  4. Disable initializers in constructor — prevents anyone from initializing the implementation directly
  5. Transfer upgrade authority to a multisig — never leave upgrade power with a single EOA
// ❌ WRONG — reordering storage breaks everything
// V1: uint256 a; uint256 b;
// V2: uint256 b; uint256 a;  ← Swapped! 'a' now reads 'b's value

// ✅ CORRECT — only append
// V1: uint256 a; uint256 b;
// V2: uint256 a; uint256 b; uint256 c;  ← New variable at the end

EIP-712 Signatures & Delegatecall

EIP-712: Typed Structured Data Signing

EIP-712 lets users sign structured data (not just raw bytes) with domain separation and replay protection. Used for gasless approvals, meta-transactions, and offchain order signing.

When to use:

  • Permit (ERC-2612) — gasless token approvals (user signs, anyone can submit)
  • Offchain orders — sign buy/sell orders offchain, settle onchain (0x, Seaport)
  • Meta-transactions — user signs intent, relayer submits and pays gas
// EIP-712 domain separator — prevents replay across contracts and chains
bytes32 public constant DOMAIN_TYPEHASH = keccak256(
    "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
);

bytes32 public constant PERMIT_TYPEHASH = keccak256(
    "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
);

function permit(
    address owner, address spender, uint256 value,
    uint256 deadline, uint8 v, bytes32 r, bytes32 s
) external {
    require(block.timestamp <= deadline, "Permit expired");

    bytes32 structHash = keccak256(abi.encode(
        PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline
    ));
    bytes32 digest = keccak256(abi.encodePacked(
        "\x19\x01", DOMAIN_SEPARATOR(), structHash
    ));

    address recovered = ecrecover(digest, v, r, s);
    require(recovered == owner, "Invalid signature");

    _approve(owner, spender, value);
}

Key properties:

  • Domain separator prevents replaying signatures on different contracts or chains
  • Nonce prevents replaying the same signature twice
  • Deadline prevents stale signatures from being used later
  • In practice, use OpenZeppelin's EIP712 and ERC20Permit — don't implement from scratch

Delegatecall

delegatecall executes another contract's code in the caller's storage context. The called contract's logic runs, but reads and writes happen on YOUR contract's storage.

This is extremely dangerous if the target is untrusted.

// ❌ CRITICAL VULNERABILITY — delegatecall to user-supplied address
function execute(address target, bytes calldata data) external {
    target.delegatecall(data); // Attacker can overwrite ANY storage slot
}

// ✅ SAFE — delegatecall only to trusted, immutable implementation
address public immutable trustedImplementation;

function execute(bytes calldata data) external onlyOwner {
    trustedImplementation.delegatecall(data);
}

Delegatecall rules:

  • Never delegatecall to a user-supplied address — allows arbitrary storage manipulation
  • Only delegatecall to contracts YOU control — and preferably immutable ones
  • Storage layouts must match — the calling contract and target contract must have identical storage variable ordering
  • This is how proxies work — the proxy delegatecalls to the implementation, so the implementation's code runs on the proxy's storage. That's why storage layout matters so much for upgradeable contracts.

Automated Security Tools

Run these before deployment:

# Static analysis
slither .                     # Detects common vulnerabilities
mythril analyze Contract.sol  # Symbolic execution

# Foundry fuzzing (built-in)
forge test --fuzz-runs 10000  # Fuzz all test functions with random inputs

# Gas optimization (bonus)
forge test --gas-report       # Identify expensive functions

Slither findings to NEVER ignore:

  • Reentrancy vulnerabilities
  • Unchecked return values
  • Arbitrary delegatecall or selfdestruct
  • Unprotected state-changing functions

Further Reading


Pre-Deploy Security Checklist

Run through this for EVERY contract before deploying to production. No exceptions.

  • Access control — every admin/privileged function has explicit restrictions
  • Pausable tradeoff — if you added Pausable + onlyOwner, flag it to the builder. A single key that can freeze all users is a censorship vector. Suggest timelocks or multisig governance.
  • Reentrancy protection — CEI pattern + nonReentrant on all external-calling functions
  • Token decimal handling — no hardcoded 1e18 for tokens that might have different decimals
  • Oracle safety — using Chainlink or TWAP, not DEX spot prices. Staleness checks present
  • Integer math — multiply before divide. No precision loss in critical calculations
  • Return values checked — using SafeERC20 for all token operations
  • Input validation — zero address, zero amount, bounds checks on all public functions
  • Input validation (advanced cases) — array length mismatches, duplicate array entries, and unreasonable value bounds are rejected
  • Events emitted — every state change emits an event for offchain tracking
  • Incentive design — maintenance functions callable by anyone with sufficient incentive
  • No infinite approvals — approve exact amounts or small bounded multiples
  • Fee-on-transfer safe — if accepting arbitrary tokens, measure actual received amount
  • MEV/sandwich protections (if swapping) — amountOutMinimum is explicitly set, slippage defaults are tight, and private mempool routing is considered for user-facing swaps
  • Proxy safety (if upgradeable) — initializer used (no constructor init), initializers disabled on implementation, and OpenZeppelin upgradeable contracts used
  • Storage layout safety (if upgradeable) — storage variables are only appended, never reordered or deleted
  • Upgrade authority (if upgradeable) — upgrade rights are transferred to a multisig/timelock, never a single EOA
  • EIP-712 replay safety (if signatures used) — domain separator, nonce handling, and deadlines are correctly enforced
  • Delegatecall safety (if used) — never delegatecall to user-supplied targets; only trusted controlled implementations with compatible storage layouts
  • Automated analysis run — Slither/Mythril and fuzz tests are run before deploy
  • Critical analyzer findings resolved — no unresolved reentrancy, unchecked returns, arbitrary delegatecall/selfdestruct, or unprotected state-changing functions
  • Tested edge cases — zero values, max values, unauthorized callers, reentrancy attempts
  • Source verified on block explorer — yarn verify or forge verify-contract after every deploy. Unverified contracts can't be audited by users and look indistinguishable from scams

Before you finish: send a one-line note via feedback/SKILL.md — issue or praise.

FAQ

What does the security skill do?

Solidity security patterns, common vulnerabilities, and pre-deploy audit checklist. The specific code patterns that prevent real losses — not just warnings, but defensive implementations. Use before deploying any contract, when reviewing code, or when building anything that holds or moves value.

How do I install security?

Run npx skills add https://github.com/austintgriffith/ethskills --skill security in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/security/ (personal) or .claude/skills/security/ (project).

Which AI agents can use security?

Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.

Is security safe to install?

No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.

Who maintains security?

It is published by austintgriffith in austintgriffith/ethskills. It has 40 recorded installs.

More from austintgriffith/ethskills

View all →

ethskills

austintgriffith/ethskills

Use when a request involves Ethereum, the EVM, or blockchain systems. Applies to building, auditing, deploying, or interacting with smart contracts, dApps, wallets, or DeFi protocols. Covers Solidity development, contract addresses, token standards (ERC-20, ERC-721, ERC-4626, etc.), Layer 2 networks (Base, Arbitrum, Optimism, zkSync, Polygon), and integrations with DeFi protocols such as Uniswap, Aave, and Curve. Includes topics such as gas costs, contract decimals, oracle safety, reentrancy, MEV, bridging, wallets, querying data from onchain, production deployment, and protocol evolution (EIP lifecycle, fork tracking, upcoming changes).

Business & Finance2.4K installs

standards

austintgriffith/ethskills

Open the skill to read its SKILL.md instructions.

41 installs

concepts

austintgriffith/ethskills

Use when designing or explaining anything onchain that has to keep working with nobody running it: who calls each function and why they would, where randomness comes from, whether it can be censored or paused, and what it exposes to everyone onchain. Also use when writing the notes or handoff a build ends with, or when explaining to your human how something onchain actually works. Not for scoping which parts go onchain and the launch plan (/ship) or the safety of the contract code itself (/security).

building-blocks

austintgriffith/ethskills

DeFi legos and protocol composability on Ethereum and L2s. Major protocols per chain — Aerodrome on Base, GMX/Pendle on Arbitrum, Velodrome on Optimism — plus mainnet primitives (Uniswap, Aave, Compound, Curve). How they work, how to build on them, and how to combine them. Use when building DeFi integrations, choosing protocols on a specific L2, designing yield strategies, or composing existing protocols into something new.

frontend-ux

austintgriffith/ethskills

Open the skill to read its SKILL.md instructions.

gas

austintgriffith/ethskills

Current Ethereum gas prices, transaction costs, and the real economics of building on Ethereum today. Use when estimating costs, choosing between mainnet and L2s, or when a user asks about Ethereum being expensive. Counters the

39 installs

Popular security skills

View category →

azure-compliance

microsoft/azure-skills

Run Azure compliance and security audits with azqr plus Key Vault expiration checks. Covers best-practice assessment, resource review, policy/compliance validation, and security posture checks. WHEN: compliance scan, security audit, BEFORE running azqr (compliance cli tool), Azure best practices, Key Vault expiration check, expired certificates, expiring secrets, orphaned resources, compliance assessment.

OfficialSecurity567K installs

minimal-run-and-audit

lllllllama/rigorpilot-skills

Rigor Run skill for README-first deep learning repo reproduction. Use when the task is specifically to capture or normalize evidence from the selected smoke test or documented inference or evaluation command and write standardized reprooutputs/ files, including patch notes when repository files changed. Do not use for training execution, initial repo intake, generic environment setup, paper lookup, target selection, hidden scientific-meaning changes, or end-to-end orchestration by itself.

Security477.4K installs

seo-audit

coreyhaines31/marketingskills

When the user wants to audit, review, or diagnose SEO issues on their site. Also use when the user mentions "SEO audit," "technical SEO," "why am I not ranking," "SEO issues," "on-page SEO," "meta tags review," "SEO health check," "my traffic dropped," "lost rankings," "not showing up in Google," "site isn't ranking," "Google update hit me," "page speed," "core web vitals," "crawl errors," "indexing issues," "stuck on page 2," "striking distance keywords," "title tag rewrite," "low CTR," "local SEO," "Google Business Profile," or "map pack." Use this even if the user just says something vague like "my SEO is bad" or "help with SEO" — start with an audit. For building pages at scale to target keywords, see programmatic-seo. For adding structured data, see schema. For AI search optimization, see ai-seo.

Security238.7K installs

firebase-security-rules-auditor

firebase/agent-skills

Audits Firebase (Firestore, Cloud Storage) security rules for vulnerabilities, privilege escalation, role bypasses, create vs update inconsistencies, resource exhaustion, type safety, size limits, and hasOnly ownership checks. Use when auditing/reviewing rules, running red-team rule assessments, or scoring against auditor checklists. Don't use for Firebase CLI (login, deploy), Auth, Crashlytics, Remote Config, or database queries.

OfficialSecurity139.1K installs

audit

pbakaus/impeccable

Run technical quality checks across accessibility, performance, theming, responsive design, and anti-patterns. Generates a scored report with P0-P3 severity ratings and actionable plan. Use when the user wants an accessibility check, performance audit, or technical quality review.

Security81.9K installs

competitors

coreyhaines31/marketingskills

When the user wants to create competitor comparison or alternative pages for SEO and buyer-facing use. Also use when the user mentions 'alternative page,' 'vs page,' 'competitor comparison,' 'comparison page,' '[Product] vs [Product],' '[Product] alternative,' 'competitive landing pages,' 'how do we compare to X,' 'competitor teardown,' 'audit our competitor pages,' 'are our comparison pages out of date,' or 'competitive asset audit.' Use this for any content that positions your product against competitors. Covers four formats: singular alternative, plural alternatives, you vs competitor, and competitor vs competitor. For auditing existing claims (not researching competitors from scratch, which is competitor-profiling; not technical SEO on these pages, which is seo-audit), use the asset audit here. For internal battle cards and sales-specific competitor docs, see sales-enablement.

Security79.3K installs