Cosmos Vulnerability Scanner
Trail of BitsOfficialPublished by Trail of Bits
- Skill
- Workflow
- Security
- CC-BY-SA-4.0
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
The Cosmos Vulnerability Scanner is an automated security auditing tool designed for Cosmos SDK-based blockchains. It performs a multi-phase analysis of custom modules and smart contracts to identify vulnerabilities that could lead to chain halts, state divergence, or loss of funds. The skill orchestrates parallel scanning agents that evaluate code against specific patterns related to core SDK logic, IBC integrations, EVM compatibility, and CosmWasm contracts. It emphasizes consensus-critical execution paths, ensuring that findings are relevant to the blockchain's stability and security.
What it does
- Generates a technical inventory and threat model via a discovery phase.
- Spawns parallel scanning agents to analyze code against predefined vulnerability patterns.
- Filters findings to focus exclusively on consensus-critical execution paths.
- Supports specialized scanning for IBC, EVM, and CosmWasm components.
- Writes detailed vulnerability reports as individual markdown files.
- Verifies that all required security patterns have been assessed.
When to use it
- Auditing custom Cosmos SDK modules.
- Reviewing CosmWasm smart contracts for security flaws.
- Conducting pre-launch security assessments for Cosmos-based chains.
- Investigating potential causes of chain halt incidents.
Advantages
- Reduces manual audit time through parallelized scanning.
- Focuses on high-impact consensus-critical bugs.
- Provides structured, actionable findings for remediation.
- Adapts to specific Cosmos SDK version requirements.
Usage examples
- “Run a security scan on the current directory.”
- The agent performs a discovery phase, identifies the platform and SDK version, executes parallel scans, and generates a report in the .bughunt_cosmos/ directory.
- “Audit my custom x/ modules and save findings to /tmp/audit_results.”
- The agent scans the codebase for consensus-critical vulnerabilities and outputs individual markdown files containing findings to the specified directory.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/trailofbits/cosmos-vulnerability-scanner/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip cosmos-vulnerability-scanner.zip -d .agents/skills
Documentation by Trail of Bits
Cosmos Vulnerability Scanner
Purpose
Scan Cosmos SDK modules and CosmWasm contracts for vulnerabilities that cause chain halts, consensus failures, or fund loss. Spawns parallel scanning agents — each specializing in a vulnerability category — that return findings to the main skill, which then writes them as individual markdown files to an output directory.
Output directory: defaults to .bughunt_cosmos/. If the user specifies a different directory in their prompt, use that instead.
When to Use
Auditing Cosmos SDK modules (custom
x/modules)Reviewing CosmWasm smart contracts
Pre-launch security assessment of Cosmos chains
Investigating chain halt incidents
When NOT to Use
Pure Solidity/EVM audits without Cosmos SDK — use Solidity-specific tools
CometBFT consensus engine internals — this covers SDK modules, not the consensus layer itself
General Go code review with no blockchain context
Cosmos SDK application logic that is not consensus-critical (e.g., CLI commands, REST endpoints)
CosmWasm contract-only audits on chains without custom SDK modules — use the CosmWasm checklist items alone
Essential Principles
Consensus path is king — A bug only matters for chain halt/fund loss if it's on the consensus-critical execution path (BeginBlock, EndBlock, FinalizeBlock, msg_server handlers, AnteHandler). Always verify a finding is reachable from consensus before reporting it.
State divergence = chain halt — Any non-determinism that causes validators to compute different state roots will halt the chain. This is the highest-severity class because it affects all validators simultaneously.
Check the version — Cosmos SDK has breaking changes across major versions (v0.47 removed GetSigners, v0.50 added ABCI 2.0, v0.53 deprecated ValidateBasic). Always check
go.modversions before applying patterns.False positives waste audit time — A map iteration in a CLI command is not a consensus bug. A panic in a query handler does not halt the chain. Verify the execution context before flagging.
Cross-module interactions are where bugs hide — The most severe findings (IBC reentrancy, EVM/Cosmos state desync, authz escalation) involve interactions between modules, not bugs within a single module.
Scanning Workflow
Phase 1: Discovery (synchronous)
Entry: Target codebase path provided by user. Codebase contains Go source (e.g., x/ modules, go.mod) or Rust contracts with cosmwasm_std.
Run a synchronous subagent (Agent tool) with the full contents of DISCOVERY.md as its prompt. The agent must:
Follow the Discovery workflow to explore the target codebase
Return the full CLAUDE.md content (the technical inventory and threat model) in its response
Return a structured summary with exactly these fields:
PLATFORM: pure-cosmos | evm | wasm (pick one; if multiple, comma-separated)
IBC_ENABLED: true | false
SDK_VERSION: <version from go.mod>
IBC_GO_VERSION: <version from go.mod, or "n/a">
CUSTOM_MODULES: <comma-separated list of x/* modules>After the subagent returns, you (the main skill) Write the CLAUDE.md to the target repo root. Save its path and the discovery values — these feed into Phase 2.
Exit: CLAUDE.md written by main skill. PLATFORM, IBC_ENABLED, SDK_VERSION, IBC_GO_VERSION, and CUSTOM_MODULES captured.
Phase 2: Parallel Vulnerability Scan
Spawn scanning agents in a single message for maximum parallelism. Use the Agent Prompt Template below, filling in the reference file for each agent. Subagents only need read access (Grep, Glob, Read) — they return findings in their response and the main skill writes the files.
Always spawn these 3 agents:
| Agent Name | Reference File | Scope |
|---|---|---|
core-scanner | VULNERABILITY_PATTERNS.md | §1-9: non-determinism, ABCI, signers, validation, handlers, ante security |
state-scanner | STATE_VULNERABILITY_PATTERNS.md | §11-23: bookkeeping, bank, pagination, events, tx replay, governance, arithmetic, encoding, deprecated modules |
advanced-scanner | ADVANCED_VULNERABILITY_PATTERNS.md | §24-27: storage keys, consensus validation, circuit breaker, crypto |
Spawn conditionally (in the same parallel message):
| Agent Name | Condition | Reference File |
|---|---|---|
evm-scanner | PLATFORM includes evm | EVM_VULNERABILITY_PATTERNS.md |
ibc-scanner | IBC_ENABLED is true | IBC_VULNERABILITY_PATTERNS.md |
cosmwasm-scanner | PLATFORM includes wasm | COSMWASM_VULNERABILITY_PATTERNS.md |
Agent Prompt Template
Construct each agent's prompt by replacing {REFERENCE_FILE_PATH} with the full path to the reference file (under {baseDir}/resources/) and {CLAUDE_MD_PATH} with the path to the CLAUDE.md written in Phase 1:
Perform a very thorough security scan of a Cosmos SDK codebase for specific vulnerability patterns.
CONTEXT:
Read {CLAUDE_MD_PATH} for codebase context (SDK version, modules, threat model, key files).
PATTERNS:
Read {REFERENCE_FILE_PATH} — it contains numbered vulnerability patterns. For EACH pattern:
1. Read the detection patterns and "What to Check" items
2. Use Grep and Glob to search the target codebase for each pattern
3. When a match is found, Read surrounding code to verify it's on a consensus-critical path (BeginBlock, EndBlock, FinalizeBlock, msg_server handlers, AnteHandler)
4. Classify severity per the guidelines below
RULES:
- Consensus path only: Only flag code reachable from consensus-critical execution. CLI/query/test code is NOT a finding.
- Check SDK version in go.mod before applying patterns (v0.47 removed GetSigners, v0.50 added ABCI 2.0, v0.53 deprecated ValidateBasic).
- Always use the Grep tool for searches, not bash grep. The reference file contains search patterns — use them directly with the Grep tool.
- Ignore cross-references to other resource files (e.g., links to IBC or COSMWASM patterns). Those patterns are covered by other scanning agents.
- Reject these rationalizations:
- "ValidateBasic catches this" — deprecated and facultative since SDK v0.53
- "Behind governance, so safe" — governance proposals can be malicious
- "IBC counterparty is trusted" — any chain can open a channel
- "Panic can't happen, input is validated" — trace the full call chain
- "Rounding error is only a few tokens" — compounds over time, can be looped
- "EVM precompile handles rollback" — many have incomplete rollback
SEVERITY:
- Critical (fund loss): signer mismatch, broken bookkeeping, AnteHandler bypass, bank keeper misuse, IBC token inflation, EVM/Cosmos desync, Merkle proof forgery, arithmetic overflow
- High (chain halt): non-determinism, ABCI panics, slow ABCI, non-deterministic IBC acks, consensus gaps, CacheContext event leak
- Medium (DoS): unbounded pagination, tx replay, missing validation, governance spam, rate limiting, circuit breaker bypass, storage key collisions
- Low (logic): rounding errors, stub handlers, event override, module ordering
OUTPUT — RETURN FORMAT:
Do NOT write any files. Return ALL findings and the summary in your response.
For each pattern, return one of:
§NUM PATTERN_NAME: Not applicable — [one-line reason]
§NUM PATTERN_NAME: FINDING (followed by the finding block below)
For each finding, include the full content using this template:
FINDING_FILE: {SEVERITY}-s{SECTION_NUM}-{kebab-description}.md
## [SEVERITY] Title
**Location**: `file:line`
**Description**: What the bug is and why it matters
**Vulnerable Code**: [snippet]
**Attack Scenario**: [numbered steps]
**Recommendation**: How to fix
**References**: [links to relevant advisories or building-secure-contracts]
You MUST report on ALL patterns in the reference file — do not skip any.Exit: All scanning agents returned. Each reported on every pattern in their reference file.
Phase 3: Write Findings
After all scanning agents return, write finding files to the output directory (default .bughunt_cosmos/):
Parse each agent's response for
FINDING_FILE:blocksFor each finding, Write the content to
{OUTPUT_DIR}/{filename}using the filename fromFINDING_FILE:Create the output directory first if it doesn't exist
Phase 4: Verify Completeness
After writing all findings, verify every pattern was assessed:
Collect the summary lines (§NUM entries) returned by each agent
Check pattern counts against expected totals:
core-scanner: 8 patterns (§1-9, excluding §8 legacy-only)state-scanner: 13 patterns (§11-23)advanced-scanner: 4 patterns (§24-27)evm-scanner(if spawned): 10 patterns (§1-10)ibc-scanner(if spawned): 16 patterns (§1-16)cosmwasm-scanner(if spawned): 3 patterns (§1-3)
If any pattern is missing from a summary, flag it and re-prompt that agent
List all finding files written to the output directory with a
Globfor*.md
Exit: All patterns accounted for. Finding files listed for the user.
Success Criteria
[ ] Discovery CLAUDE.md written with complete technical inventory and threat model
[ ] All scanning agents completed and reported on every pattern in their reference file
[ ] Pattern counts verified against expected totals (no patterns skipped)
[ ] All findings written to output directory as individual markdown files
[ ] Each finding file includes: severity, location, vulnerable code, attack scenario, recommendation
Resources
Discovery & CLAUDE.md: DISCOVERY.md
Core patterns (§1-9): VULNERABILITY_PATTERNS.md
State & module patterns (§11-23): STATE_VULNERABILITY_PATTERNS.md
Advanced patterns (§24-27): ADVANCED_VULNERABILITY_PATTERNS.md
IBC vulnerabilities: IBC_VULNERABILITY_PATTERNS.md
CosmWasm vulnerabilities: COSMWASM_VULNERABILITY_PATTERNS.md
EVM vulnerabilities: EVM_VULNERABILITY_PATTERNS.md
Building Secure Contracts:
building-secure-contracts/not-so-smart-contracts/cosmos/Cosmos SDK Docs: https://docs.cosmos.network/
CodeQL for Cosmos SDK: https://github.com/crypto-com/cosmos-sdk-codeql
Files
- ATTRIBUTION.mdAdded by AutoGineer975 B
- CHANGELOG.md12.6 KiB
- LICENSELicence file added by AutoGineer19.7 KiB
- SKILL.md10.7 KiB
- agents/openai.yaml256 B
- assets/trail-of-bits-mark.svg3.0 KiB
- resources/ADVANCED_VULNERABILITY_PATTERNS.md12.5 KiB
- resources/COSMWASM_VULNERABILITY_PATTERNS.md7.5 KiB
- resources/DISCOVERY.md2.9 KiB
- resources/EVM_VULNERABILITY_PATTERNS.md18.0 KiB
- resources/IBC_VULNERABILITY_PATTERNS.md27.5 KiB
- resources/STATE_VULNERABILITY_PATTERNS.md29.2 KiB
- resources/VULNERABILITY_PATTERNS.md26.4 KiB
resources/STATE_VULNERABILITY_PATTERNS.md
State & Module Vulnerability Patterns
Updated for Cosmos SDK v0.53.x. State management, module interaction, and data handling patterns (§11-23) covering bookkeeping, bank keeper, pagination, events, transaction replay, governance, arithmetic, encoding, and deprecated modules. Referenced by SKILL.md checklist items via
§anchors.
11 BROKEN BOOKKEEPING
Description: Custom internal accounting alongside x/bank module becomes inconsistent when direct token transfers bypass internal bookkeeping.
Detection Patterns:
// VULNERABLE: Internal bookkeeping separate from x/bank
type Keeper struct {
// Internal tracking of balances
userBalances map[string]sdk.Coins // NOT synchronized with x/bank!
}
func (k Keeper) Deposit(ctx context.Context, user string, amount sdk.Coins) {
// Updates internal bookkeeping
k.userBalances[user] = k.userBalances[user].Add(amount...)
// Also updates x/bank
k.bankKeeper.SendCoins(ctx, sender, moduleAccount, amount)
}
// PROBLEM: Direct IBC transfer bypasses internal bookkeeping!
// User receives tokens via IBC -> x/bank updated but userBalances not updated
// Invariant violated: sum(userBalances) != bankKeeper.GetSupply()What to Check:
[ ] No custom balance tracking alongside x/bank
[ ] OR custom tracking uses blocklist to prevent unexpected transfers
[ ] Invariant checks compare internal accounting to x/bank
[ ] Module accounts use SendEnabled parameter
Mitigation:
// OPTION 1: Use blocklist to prevent unexpected transfers
func (k Keeper) BeforeTokenTransfer(ctx context.Context, from, to string) error {
if !k.IsAuthorizedTransfer(ctx, from, to) {
return errors.New("direct transfers blocked")
}
return nil
}
// OPTION 2: Use SendEnabled parameter
// In x/bank params, set SendEnabled = false for your token
// OPTION 3: Don't maintain separate bookkeeping
// Use x/bank as source of truth, query when needed
func (k Keeper) GetUserBalance(ctx context.Context, user string) sdk.Coins {
addr := sdk.AccAddress(user)
return k.bankKeeper.GetAllBalances(ctx, addr)
}Invariant Testing:
func (k Keeper) InvariantCheck(ctx sdk.Context) error {
internalTotal := sdk.NewCoins()
k.IterateBalances(ctx, func(addr string, balance sdk.Coins) bool {
internalTotal = internalTotal.Add(balance...)
return false
})
moduleBalance := k.bankKeeper.GetAllBalances(ctx, k.moduleAccount)
if !internalTotal.Equal(moduleBalance) {
return fmt.Errorf("bookkeeping mismatch: internal=%v bank=%v",
internalTotal, moduleBalance)
}
return nil
}References: building-secure-contracts/not-so-smart-contracts/cosmos/broken_bookkeeping
12 BANK KEEPER & TOKEN SECURITY
Description: Any module with access to x/bank's keeper can mint, burn, and transfer tokens at will. The bank module provides blocklist, SendEnabled, and SendRestriction mechanisms to control this, but they must be configured correctly.
What to Check:
[ ] Modules with bank keeper access: all methods are fully exposed — review every usage for unauthorized mint/burn/transfer
[ ] Blocklist prevents unexpected transfers to module accounts
Addresses blocked during startup via bank keeper's argument. Default blocked addresses come from
maccPermsviaBlockedAddresses()x/govmodule address explicitly removed from blocklist (receives proposal deposits) — verify custom chains make the same intentional exclusionsBlocklist explicitly checked whenever new transfer functionality is implemented
[ ]
SendEnabledprevents unexpected transfers of specific denominations.DefaultSendEnabledis true by default[ ]
SendRestrictionfunctions (AppendSendRestriction,PrependSendRestriction) reviewed if used. Order matters (Prepend vs Append).ClearSendRestrictionremoves ALL restrictions. Since v0.53.0,SendCoinschecks restrictions BEFORE deducting coins (#24106, #24053)[ ]
SpendableCoinsused instead ofGetBalancefor accounts with locked/vesting funds[ ] Deflationary tokens considered in transfer accounting
[ ] If
x/mintuses customMintFn(viakeeper.WithMintFn), no unbounded minting or inflation bypass. OldInflationCalculationFnmust benil
References: Desmos blocklist vulnerability
13 UNBOUNDED PAGINATION / QUERY DoS
Description: gRPC query endpoints using types/query.Paginate accept a Limit field from clients with no enforced maximum. A malicious client can set Limit: math.MaxUint64, forcing the node to iterate over the entire store in a single request, causing OOM or extended blocking. The SDK's DefaultLimit is 100, but it is only applied when Limit == 0 — any non-zero value is accepted as-is. Recent fix d9d77304fd addressed a related issue in x/auth/tx and x/bank genesis, but the core types/query/pagination.go still lacks a hard cap.
Detection Patterns:
// VULNERABLE: No max limit enforcement
func (k Keeper) AllRecords(ctx context.Context, req *types.QueryAllRecordsRequest) (*types.QueryAllRecordsResponse, error) {
store := k.storeService.OpenKVStore(ctx)
// Client controls req.Pagination.Limit with no upper bound!
results, pageRes, err := query.CollectionPaginate(ctx, k.Records, req.Pagination,
func(key string, value types.Record) (types.Record, error) {
return value, nil
},
)
// ...
}What to Check:
[ ] All gRPC query handlers enforce a maximum pagination limit
[ ] Custom query endpoints that iterate stores have bounded iteration
[ ]
CountTotal = truerequests are bounded (counting all records is expensive)[ ] Genesis export with large state is tested
Mitigation:
// SECURE: Enforce maximum pagination limit
const MaxPaginationLimit = 1000
func sanitizePagination(req *query.PageRequest) *query.PageRequest {
if req == nil {
req = &query.PageRequest{}
}
if req.Limit == 0 || req.Limit > MaxPaginationLimit {
req.Limit = MaxPaginationLimit
}
return req
}
func (k Keeper) AllRecords(ctx context.Context, req *types.QueryAllRecordsRequest) (*types.QueryAllRecordsResponse, error) {
req.Pagination = sanitizePagination(req.Pagination)
// ...
}14 EVENT OVERRIDE / SUPPRESSION
Description: Cosmos SDK v0.53 introduced EventManager.OverrideEvents() which replaces all previously emitted events with a new set. If called in module code (especially in message handlers or ABCI hooks), it can silently suppress events emitted by earlier modules in the same block or by the SDK framework itself. This can break indexers, IBC relayers, and monitoring tools that depend on events.
Detection Patterns:
// VULNERABLE: Overriding events suppresses earlier module events
func (ms msgServer) Execute(ctx context.Context, msg *types.MsgExecute) (*types.MsgExecuteResponse, error) {
sdkCtx := sdk.UnwrapSDKContext(ctx)
// This wipes ALL events emitted so far in this transaction/block!
sdkCtx.EventManager().OverrideEvents(sdk.Events{myEvent})
// Events from auth, bank, etc. are now gone
}What to Check:
[ ] No
OverrideEvents()calls in module message handlers or ABCI methods[ ] If
OverrideEvents()is used, verify it only operates on a child context's EventManager, not the parent[ ] Events from prior modules are not silently dropped
[ ] IBC relayer and indexer compatibility verified after event changes
Mitigation:
// SECURE: Use EmitTypedEvent to add events, never OverrideEvents
func (ms msgServer) Execute(ctx context.Context, msg *types.MsgExecute) (*types.MsgExecuteResponse, error) {
sdkCtx := sdk.UnwrapSDKContext(ctx)
sdkCtx.EventManager().EmitTypedEvent(&types.EventExecute{...})
// Preserves all existing events
}15 UNORDERED TRANSACTION REPLAY
Description: Cosmos SDK v0.53 supports unordered transactions (TxBody.Unordered = true) with timeout-timestamp-based replay protection instead of sequence numbers. These transactions use nonce tracking in x/auth with a PreBlocker that cleans up expired nonces. Vulnerabilities arise from: (1) insufficient timeout duration allowing replay after nonce cleanup, (2) chains that don't enable the UnorderedTxDecorator or set maxTxTimeoutDuration too high, (3) nonce storage DoS by flooding with unique unordered transactions.
Detection Patterns:
// VULNERABLE: Unordered tx support enabled without proper timeout limits
func NewAnteHandler(options HandlerOptions) sdk.AnteHandler {
decorators := []sdk.AnteDecorator{
ante.NewSigVerificationDecorator(
options.AccountKeeper,
options.SignModeHandler,
// Missing: WithMaxUnorderedTxTimeoutDuration
// Missing: WithUnorderedTxGasCost
),
}
}
// VULNERABLE: Application logic assumes sequence-based ordering
func (k Keeper) ProcessUserAction(ctx context.Context, msg *types.MsgAction) error {
// With unordered txs, multiple actions from the same sender
// can execute in any order within the same block!
lastAction := k.GetLastAction(ctx, msg.Sender)
// This ordering assumption breaks with unordered txs
}What to Check:
[ ]
WithMaxUnorderedTxTimeoutDurationis set to a reasonable value (default: 10 minutes)[ ]
WithUnorderedTxGasCostis configured (default: 2240 gas) to prevent nonce flooding[ ] Application logic doesn't assume transaction ordering within a block
[ ] PreBlocker includes
RemoveExpiredUnorderedNoncescleanup[ ] State-dependent operations handle concurrent unordered transactions safely
Mitigation:
// SECURE: Configure unordered tx parameters
ante.NewSigVerificationDecorator(
options.AccountKeeper,
options.SignModeHandler,
ante.WithMaxUnorderedTxTimeoutDuration(10 * time.Minute),
ante.WithUnorderedTxGasCost(2240),
)
// SECURE: Don't rely on transaction ordering
func (k Keeper) ProcessUserAction(ctx context.Context, msg *types.MsgAction) error {
// Use idempotent operations or explicit ordering fields
// instead of relying on sequence-based ordering
}16 MESSAGE PRIORITY / PROPOSAL ORDERING
Description: Missing prioritization of critical messages (oracle updates, emergency pause, governance) allows front-running and censorship during network congestion. In Cosmos SDK v0.50+ with ABCI 2.0, the block proposer controls transaction ordering via PrepareProposal and ProcessProposal, making this even more important.
Detection Patterns:
// VULNERABLE: No priority for critical oracle update in CheckTx
func (app *App) CheckTx(req *abci.CheckTxRequest) (*abci.CheckTxResponse, error) {
// All messages treated equally, oracle updates can be delayed
return app.BaseApp.CheckTx(req)
}
// VULNERABLE: PrepareProposal does not prioritize critical messages
func (app *App) PrepareProposal(req *abci.PrepareProposalRequest) (*abci.PrepareProposalResponse, error) {
// Default ordering - no special treatment for emergency messages
return app.BaseApp.PrepareProposal(req)
}What to Check:
[ ] Oracle/price feed updates have high priority
[ ] Emergency pause/circuit breaker messages prioritized
[ ] Critical governance proposals prioritized
[ ]
PrepareProposalconsiders message priority when ordering transactions[ ]
ProcessProposalvalidates that critical messages are not censored[ ] High fees required for priority transactions (prevent spam)
Mitigation:
// SECURE: Custom PrepareProposal that prioritizes critical messages
func (app *App) PrepareProposal(req *abci.PrepareProposalRequest) (*abci.PrepareProposalResponse, error) {
// Sort transactions by priority, putting critical messages first
prioritized := prioritizeTxs(req.Txs, req.MaxTxBytes)
return &abci.PrepareProposalResponse{Txs: prioritized}, nil
}
func prioritizeTxs(txs [][]byte, maxBytes int64) [][]byte {
// Decode and classify transactions
// Place oracle updates and emergency messages first
// Then normal transactions by fee
// ...
}References: building-secure-contracts/not-so-smart-contracts/cosmos/messages_priority
17 GOVERNANCE SECURITY
Description: Governance mechanisms can be abused for spam, manipulation, or fund locking.
What to Check:
[ ] If
MsgSubmitProposalis registered, submission cost is non-zero.DefaultMinInitialDepositRatiois zero (x/gov/types/v1/params.go:27) — proposals can be submitted with zero deposit by default, creating spam risk. Check bothMinDepositANDMinInitialDepositRatio[ ] If
x/govuses customCalculateVoteResultsAndVotingPowerFn(viaWithCustomCalculateVoteResultsAndVotingPowerFn), verify the tally function correctly weights votes and handles edge cases (zero voting power, abstentions, validator vs delegator votes). A buggy tally function could manipulate governance outcomes[ ] If
x/circuitmodule is enabled, the circuit breaker authority is reviewed — a compromised authority (or captured governance) can disable critical messages (e.g., unbonding, slashing), effectively locking user funds or preventing security-critical operations
18 MODULE-SPECIFIC CHECKS
Description: Known vulnerabilities, configuration pitfalls, and behavioral changes in specific SDK modules.
What to Check:
x/distribution:
[ ] Historical rewards bounded — overflow can halt chain (GHSA-p22h-3m2v-cmgh, fixed v0.53.3). Verify
incrementReferenceCountand reward accumulation on older versions/forks
x/staking:
[ ] Delegator slashing bypass fixed (GHSA-86h5-xcpx-cfqc, fixed v0.50.5) — verify slashing applies to all delegations including those created between infraction and slash execution
[ ] Panics replaced with error returns in v0.53.0 (#24391) — custom code catching panics via
recover()must handle returned errors instead
baseapp:
[ ] If
ValidateVoteExtensionshelper is used, verify chain runs >= v0.50.5. Original had bug (GHSA-95rx-m9m5-m94v) wherecurrentHeight/chainIDargs were misused. In fixed version they're ignored (read from context). In v0.51+ they're removed[ ] If
DisableBlockGasMeteris enabled (baseapp.DisableBlockGasMeter()), per-block gas limits removed — only CometBFT'smax_bytesapplies. Removes DoS protection layer; only use if gas metered externally[ ] If Optimistic Execution enabled (
baseapp/oe/),FinalizeBlocklogic must not have side effects outside SDK state (external API calls, file writes) — speculative execution of rejected blocks cannot be rolled back[ ]
SetCheckTxHandlercan bypass standard ante handler validation if misused[ ] Custom
PrepareProposalhandlers must be deterministic in gas consumption and ordering.ProcessProposalrejections can cause consensus failures. CustomSignerExtractionAdaptermust extract signers faithfully
x/epochs:
[ ] Epoch hooks (
BeforeEpochStart,AfterEpochEnd) must not panic and must handle errors gracefully.InvokeSetHookshad a bug fixed in v0.53.2 (#24770).CurrentEpochStartHeightsemantics fixed in v0.53.0 (#24610)
x/protocolpool:
[ ] If used alongside
x/distribution, verifyCommunityPoolSpend/FundCommunityPool/CommunityPoolquery handlers are NOT called (they return errors). Funds can get stuck if migration from distribution's community pool is incomplete
ABCIListener / StreamingManager:
[ ] If
SetStreamingManageris used, listeners must not panic (halts chain), must not be excessively slow (synchronous during block processing), and must tolerate rejected blocks — especially with Optimistic Execution
Other:
[ ] No custom storage/cache implemented — may fail to revert after failed transactions
[ ]
errorsmod.Register(fromcosmossdk.io/errors) used for errors, not hand-rolled mechanisms[ ] CometBFT config/genesis.json not overwritten without validation; genesis.json save is atomic
19 MODULE INITIALIZATION ORDERING
Description: Cosmos SDK modules must be initialized in a specific order. Incorrect ordering can cause panics during genesis, missing state, or security bypasses.
What to Check:
[ ]
SetOrderPreBlockers:upgrademodule must be first.authmust also be in PreBlockers (required for unordered tx cleanup viaRemoveExpiredUnorderedNonces)[ ]
SetOrderBeginBlockers/SetOrderEndBlockers:x/epochsis in BeginBlockers and fires epoch hooks — modules depending on epoch events must come afterepochs. Note:capabilitymodule has been removed from cosmos-sdk (moved to ibc-go)[ ]
SetOrderInitGenesis:genutilsafterstaking(pool initialization);genutilsafterauth(param access)[ ]
SetOrderExportGenesis:protocolpoolmust be exported beforebank[ ] Keepers passed to other keepers are initialized. Suspicious pattern: keeper initialized → uninitialized copy passed to another → first one assigned to
app.TheKeeper
20 ARITHMETIC ERRORS
Description: Arithmetic bugs in financially-critical code — rounding errors, wrapping overflows, wrong operand selection, and missing sign/bound validation. Any deviation directly translates to fund loss. math.LegacyDec (formerly sdk.Dec) has precision issues and lacks associativity.
Detection Patterns:
Pattern 1: Rounding Errors (Precision Loss)
// VULNERABLE: Division before multiplication (loses precision)
sharePrice := totalValue.Quo(totalShares) // Division first
userValue := sharePrice.Mul(userShares) // Multiplication second
// User gets less value due to rounding down in division
// VULNERABLE: math.LegacyDec associativity issues
result1 := a.Mul(b).Quo(c) // (1 * 10) / 100 = 0.1
result2 := a.Quo(c).Mul(b) // (1 / 100) * 10 = 0.1 (but different precision!)
// result1 != result2 due to precision handling
// VULNERABLE: Repeated rounding favors users
for _, user := range users {
reward := totalReward.Quo(math.LegacyNewDec(int64(len(users))))
k.MintReward(ctx, user, reward)
}
// Total minted > totalReward due to rounding upPattern 2: Wrong Operand in Calculation
// VULNERABLE: Osmosis LP share miscalculation (Finding 9, $5M loss)
// MaximalExactRatioJoin multiplied shareRatio * userTokens
// instead of shareRatio * totalPoolLiquidity
shareRatio := userTokens.Quo(totalPoolLiquidity)
newShares := shareRatio.Mul(userTokens) // WRONG: should be shareRatio.Mul(totalShares)
// Users received 50% more LP shares than deservedPattern 3: Missing Sign/Bound Validation
// VULNERABLE: Vesting Account Poisoning (Barberry, Finding 19)
func (msg MsgCreatePeriodicVestingAccount) ValidateBasic() error {
for _, period := range msg.VestingPeriods {
if period.Length <= 0 {
return errors.New("period length must be positive")
}
// Missing: validation that period.Amount coins are all positive!
// Negative amounts cause LockedCoins() to panic on withdrawal
}
}What to Check:
[ ] Multiplication before division pattern used
[ ] Rounding direction favors protocol, not users
[ ] No repeated rounding in loops
[ ] Consistent calculation order across all operations
[ ] Consider using integer arithmetic with scaling factor
[ ] Operands in share/ratio calculations are verified against the formula (not swapped)
[ ] CosmWasm wrapping overflow: see COSMWASM_VULNERABILITY_PATTERNS.md §1
[ ] All amount/coin fields validated for positive values (not just positive length/duration) — negative amounts in vesting periods cause permanent fund freeze
[ ] Front-running of account creation reviewed — malicious vesting accounts can be created at victim addresses before the victim's first transaction
Mitigation:
// SECURE: Multiply before divide (preserves precision)
userValue := totalValue.Mul(userShares).Quo(totalShares)
// SECURE: Round in favor of system
reward := totalReward.Mul(userShares).QuoTruncate(totalShares)
// SECURE: Round up when calculating fees (users pay slightly more)
fee := amount.Mul(feeRate).QuoRoundUp(math.LegacyNewDec(10000))
// SECURE: Distribute with remainder handling
totalDistributed := math.LegacyZeroDec()
for i, user := range users {
var reward math.LegacyDec
if i == len(users)-1 {
reward = totalReward.Sub(totalDistributed)
} else {
reward = totalReward.Quo(math.LegacyNewDec(int64(len(users))))
totalDistributed = totalDistributed.Add(reward)
}
k.MintReward(ctx, user, reward)
}
// SECURE: Validate all coin amounts are positive
for _, period := range msg.VestingPeriods {
if !period.Amount.IsAllPositive() {
return errors.New("all period amounts must be positive")
}
}References:
building-secure-contracts/not-so-smart-contracts/cosmos/rounding_errors
Osmosis $5M exploit (Finding 9)
Barberry / Vest in Peace (Finding 19)
21 DATA VALIDATION & ENCODING
Description: Encoding, validation, and type handling issues that can cause panics, state corruption, or chain halts.
What to Check:
[ ]
Coins/DecCoinsalways sorted and non-negative — SDK relies on this forAdd,IsAnyGTE, and genesis state loading[ ] Unicode in
Coinsdenominations handled correctly. Differences betweenCoinvsCoinstypes understood[ ] Since v0.53,
Coins.AmountOf()no longer validates the denom and returns zero for missing denoms (no panic).AmountOfNoDenomValidationis deprecated (just callsAmountOf). Code that relied onAmountOfpanicking on invalid denom must be updated[ ]
Add,Sub,QuoonLegacyDecstill panic — use error-returning variants or validate inputs first. Using pointer toLegacyDec(*math.LegacyDec) is error-prone: Dec consists of a singlebig.Intpointer, so pointer-to-pointer adds no performance benefit and risks nil-pointer dereference[ ]
cosmossdk.io/collectionsframework: deterministic key encoding (custom key codecs produce consistent bytes),IndexedMapsecondary indexes in sync, noRemove/SetduringWalkiteration (fix: #24460), prefix/range queries used correctly[ ] Encoding enforced as early as possible — in msg server handler or protobuf definition (
stringin protobuf enforces UTF-8). Careful with invalid UTF-8 string length[ ] MustUnmarshal usage is safe
[ ]
MsgUpdateParamshandlers validate parameter bounds — invalid params (rate > 100%, negative values, zero denominators) can be silently set via governance and cause issues inBeginBlock/EndBlock[ ] Default values are safe:
Configurable parameters are set explicitly or default values are reviewed
Initial (genesis) system parameters are validated. Safety checks exist for parameters that get updated from "null" only after the first block (Umee issue)
gettermethods do not return valid values on errors — they should panic or return nil
[ ] Amino (
LegacyAmino) only for backward compatibility (legacy JSON signing, CLI proposals). New modules use protobuf exclusively[ ] If Ethereum addresses used, NOT using
HexToAddress(does not verify format, ignores errors)[ ]
AnteHandlers enforce non-zero fee to prevent spam. See Cosmos discussion[ ] Parsing of empty
Coin(s)considers any amount of extra whitespace — relevant source. For example, checkingmsg.Funds == "" || msg.Funds == "0"is incomplete; use the SDK's parsing functions instead of manual string comparison
22 EVENTS & LOGGING (INCLUDING CacheContext EVENT LEAK)
Description: Events are critical for indexers, relayers, and monitoring tools. Incorrect event handling breaks the ecosystem. A particularly dangerous pattern is the CacheContext event leak: when CacheContext is used to tentatively execute operations, state changes are correctly rolled back on failure, but events are still emitted to the parent context. Off-chain systems (bridges, indexers) trusting events without verifying on-chain state can be exploited.
Detection Patterns:
// VULNERABLE: CacheContext event leak (Huckleberry, Finding 4)
func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet) exported.Acknowledgement {
cacheCtx, writeFn := ctx.CacheContext()
// Execute in cached context — emits events to cacheCtx
err := k.processPacket(cacheCtx, packet)
if err != nil {
// State is rolled back (writeFn not called)
// BUT events from cacheCtx are still visible to parent context!
return channeltypes.NewErrorAcknowledgement(err)
}
writeFn()
return channeltypes.NewResultAcknowledgement([]byte{1})
}
// Bridge monitors see valid deposit events for transfers that never happened
// Attacker gets credited on the bridge without tokens actually movingWhat to Check:
[ ] Enough event logs to debug on-chain transactions
[ ] Events NOT generated for failed transactions — Huckleberry: IBC Event Hallucinations
[ ]
CacheContextusage reviewed: events emitted in cached context are visible even ifwriteFnis never called — state rolls back but events persist[ ] Off-chain systems (bridges, indexers, relayers) do NOT rely solely on events without verifying on-chain state
[ ] Custom IBC
OnRecvPacketthat returns error ack does not emit misleading deposit/transfer events[ ] No log injection possible
References:
23 DEPRECATED MODULES
As of v0.53.5+. Flag usage and advise migration.
x/crisis(Invariant type) — deprecated, will be removed. Use simulations (simsxframework in v0.53+) and property-based tests insteadx/nft— deprecated (#24575), moving to cosmos-legacyx/group— deprecated (#24571), moving to cosmos-legacy. EndBlocker had chain-halt bugs (GHSA-47ww-ff84-4jrg, GHSA-x5vx-95h7-rv4p, fixed v0.50.12/v0.53.0)x/params— deprecated, modules use direct keeper param storage viacollectionsValidateBasic()— deprecated and facultative (viaHasValidateBasicinterface). Validation belongs in msg server handlersed25519 tx signing — added in v0.53.0 (#24030). Chains should explicitly decide which key types to permit. Unlike secp256k1, ed25519 is not compatible with Ledger hardware wallets without custom apps
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Published by
- Trail of Bits
- Publisher
- Trail of Bits (Official)
- Source
- trailofbits/skills · plugins/building-secure-contracts/skills/cosmos-vulnerability-scanner · commit e4dd0bb
- Licence
- Creative Commons Attribution-ShareAlike 4.0 International (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Trail of Bits.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download cosmos-vulnerability-scanner.zip