Skip to content
Security
Skill

/cosmwasm

Detect bug classes specific to CosmWasm (Rust) contracts — missing info.sender authorization in execute handlers, unbounded map iteration → gas/DoS, reply/submessage reply_id confusion, migrate admin backdoors, addr_validate vs raw string addresses, unchecked info.funds, Uint128

From plugin
rugproof
1052 skills23 agents45 commands4 hooks

Claude Code

$ npx -y skills add omermaksutii/RugProof --skill cosmwasm --agent claude-code

SKILL.md

cosmwasm.SKILL.md
name: cosmwasm
description: Detect bug classes specific to CosmWasm (Rust) contracts — missing info.sender authorization in execute handlers, unbounded map iteration → gas/DoS, reply/submessage reply_id confusion, migrate admin backdoors, addr_validate vs raw string addresses, unchecked info.funds, Uint128 overflow, query reentrancy, and migration/version state. Activate on any `.rs` file with `use cosmwasm_std`, `#[entry_point]`, `ExecuteMsg`, `InstantiateMsg`, `QueryMsg`, `cw_storage_plus`, or `DepsMut`.

CosmWasm (Rust) detection

When this applies

  • Any `.rs` file importing `cosmwasm_std` (`use cosmwasm_std::{...}`)
  • Macros/attrs `#[entry_point]`, `#[cw_serde]`; enums `ExecuteMsg`, `InstantiateMsg`, `QueryMsg`, `MigrateMsg`, `SudoMsg`
  • `cw_storage_plus` types `Map`, `Item`, `IndexedMap`; `DepsMut`/`Deps`, `MessageInfo`, `Env`
  • `SubMsg`, `Reply`, `reply()`, `set_contract_version`, `cw2`

CosmWasm has no `msg.sender`-gated visibility and no implicit auth. Every `execute` branch is callable by anyone unless the handler checks `info.sender`.

Detection patterns

Missing info.sender authorization in execute (CRITICAL)

ExecuteMsg::SetConfig { admin } => {
    let mut cfg = CONFIG.load(deps.storage)?;
    cfg.admin = admin;                          // ← no info.sender check
    CONFIG.save(deps.storage, &cfg)?;
    Ok(Response::new())
}

**Signal:** an `ExecuteMsg` arm that mutates privileged state (`admin`, `owner`, `mint`, `withdraw`, `pause`) with no `if info.sender != cfg.admin { return Err(Unauthorized) }`. Anyone can dispatch any message.

Unbounded map iteration → gas exhaustion DoS (HIGH)

let all: Vec<_> = BALANCES
    .range(deps.storage, None, None, Order::Ascending)   // ← whole map
    .collect::<StdResult<_>>()?;
for (addr, bal) in all { /* mutate each */ }

**Signal:** `.range(.., None, None, ..)` / `.keys(..)` over a user-growable `Map` inside `execute`, or a loop whose length attackers control. Each entry costs gas; an attacker inflates the map until the handler always runs out of gas (permanent freeze). Paginate with `start_after` + `limit`.

reply_id confusion / unvalidated submessage reply (HIGH)

#[entry_point]
pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result<Response, ContractError> {
    let res = msg.result.unwrap();              // ← assumes success; panics on err
    // no match on msg.id → all replies handled identically
    parse_instantiate_response_data(&res.data.unwrap())?;
    Ok(Response::new())
}

**Signal:** a `reply` handler that doesn't `match msg.id { ... }` against known reply IDs, or `.unwrap()`s `msg.result`/`msg.result.data`, or trusts `SubMsgResult` data without checking it came from the expected submessage. Different submessages can land in the same reply with attacker-influenced data.

migrate admin backdoor / unguarded migrate (HIGH)

#[entry_point]
pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result<Response, ContractError> {
    CONFIG.save(deps.storage, &Config { admin: msg.new_admin })?;  // ← arbitrary rewrite
    Ok(Response::new())                          // no version/sender guard
}

**Signal:** `migrate` that overwrites owner/config from `MigrateMsg` without checking `set_contract_version`/`get_contract_version` (downgrade or replay), or a centralization risk where the code-admin can swap logic+state at will. Even with chain-level migrate auth, document who holds the admin.

addr_validate vs raw string address (MEDIUM-HIGH)

let recipient = msg.to;                          // ← String, never validated
BALANCES.save(deps.storage, &Addr::unchecked(&recipient), &amt)?;  // ← unchecked

**Signal:** `Addr::unchecked(...)` on user input, or storing/using a `String` address without `deps.api.addr_validate(&s)?`. Unvalidated/un-normalized addresses split balances across casing/bech32 variants and break invariants.

Unchecked info.funds (HIGH)

ExecuteMsg::Deposit {} => {
    let amount = msg.amount;                     // ← trusts a field, not info.funds
    CREDIT.save(deps.storage, &info.sender, &amount)?;
    Ok(Response::new())
}

**Signal:** crediting a deposit from a message field instead of `info.funds`, or not asserting `info.funds` contains the expected denom AND amount (`must_pay`/`one_coin` from `cw_utils`). Also flag handlers that ignore unexpected attached funds.

Uint128 / Uint256 overflow & raw arithmetic (HIGH)

let total = a + b;                               // ← Uint128 Add can overflow → error,
let scaled = price * qty;                        //    but `as`/u128 casts wrap silently

**Signal:** `Uint128`/`Uint256` `+`/`-`/`*` where overflow should be a domain error vs a panic, OR casting to/from primitive `u128`/`u64` with `as`/`.u128()` in accounting. Prefer `checked_add`/`checked_sub`/`checked_mul` returning `OverflowError`.

Query reentrancy / cross-contract query trust (MEDIUM)

let price: PriceResp = deps.querier.query_wasm_smart(oracle, &QueryMsg::Price {})?;

**Signal:** acting on a `query_wasm_smart` result from a user-supplied contract address, or assuming queries are side-effect-free guarantees (a malicious queried contract returns adversarial data). Validate the oracle/contract address against an allowlist.

Severity rubric

| Pattern | Severity | Notes | |---|---|---| | Execute arm mutating privileged state, no `info.sender` check | **Critical** | Anyone seizes admin/funds | | Unbounded `.range(None,None)` over user-growable map | **High** | Permanent gas-DoS freeze | | `reply` without `match msg.id` / unwrap of result | **High** | Cross-submsg data confusion | | `migrate` overwrites owner/config unguarded | **High** | Logic+state backdoor | | Deposit credited from field, not `info.funds` | **High** | Free credit / fund theft | | `Uint128` overflow / lossy primitive cast | **High** | Value-dependent | | `Addr::unchecked` on user input | **Medium** | A

Read more
@omermaksutii
Ships withrugproof

Rugproof your code before someone else does. 🌐 Live site: omermaksutii.github.io/RugProof 📦 Latest: v1.0.0 — 45 commands · 23 agents · 45 skills · 13 MCP servers · tested, offline-first, with rule packs, a benchmark, non-EVM coverage, and post-deploy

1052 skills23 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
10
Star history of omermaksutii/RugProof on GitHub, a chart by star-history.com

Other skills on rugproof.

More skills from other plugins.

api-security

api-security

Use for authorized security assessment of REST, GraphQL, WebSocket, or SOAP APIs, including discovery, authentication,…

@zhaoxuya520@zhaoxuya520Install Skill
nemoclaw-user-guide

nemoclaw-user-guide

Find official NemoClaw documentation for installation, configuration, operation, or troubleshooting. Also handles requested docs…

nemoclaw11d23k
@nvidia@nvidiaInstall Skill

Questions about cosmwasm.

Which coding agents can use cosmwasm?
cosmwasm installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.