EXAMPLE FINDING

Missing Access Control in a CosmWasm Contract

This is what an Odin Scan finding looks like, end to end: the code, the severity, how an attacker would exploit it, a working proof of concept, and the fix. It comes from our own rule corpus for CosmWasm access control, so you can verify every line yourself.

CRITICALAccess ControlCosmWasm

Function modifies contract state without an access control check

update_config writes the contract's CONFIG item without asserting the caller is the owner or an admin. Any account that can execute the contract can call it.

The vulnerable code

pub fn update_config(
    deps: DepsMut,
    new_config: Config,
) -> StdResult<Response> {
    // No assert_owner / assert_admin check: anyone can call this
    CONFIG.save(deps.storage, &new_config)?;
    Ok(Response::default())
}

Exploit sketch

  1. Attacker inspects the contract's exported execute messages and finds update_config accepts an arbitrary Config.
  2. They craft an execute message setting a fee wallet, proxy, or spend limit they control.
  3. One transaction later, every subsequent contract interaction routes value or privileges to the attacker. No keys, no bridge, no flash loan needed - just an unprotected message.

Proof of concept

With any Cosmos SDK wallet and the contract address:

# Any address can update the config - no owner signature required
wasmd tx wasm execute <CONTRACT_ADDR> \
  '{"update_config":{"new_config":{"fee_wallet":"attacker_addr","fee_bps":10000}}}' \
  --from attacker --chain-id <CHAIN_ID>

The fix

Assert ownership before mutating state. With cw-ownable:

pub fn update_config(
    deps: DepsMut,
    info: MessageInfo,
    new_config: Config,
) -> StdResult<Response> {
    cw_ownable::assert_owner(deps.storage, &info.sender)?;
    CONFIG.save(deps.storage, &new_config)?;
    Ok(Response::default())
}

How Odin Scan catches it

The deterministic rule missing_access_control flags any function that writes storage items while its body lacks a recognized authorization call (cw_ownable::assert_owner, cw_controllers admin checks, or a custom sender check). On the OdinBench suite this rule's category scored 100% F1 on synthetic missing-access-control contracts after the verification pass. Run it yourself on the live demo - paste the vulnerable snippet above and you will get this finding in about three minutes.

Every finding ships like this: severity, exploit path, PoC, fix.

Try the no-account demo, or run the full multi-model scan on your whole repository with a 7-day Pro trial.