## Core Vulnerability Patterns & Audit Checks

> Updated for Cosmos SDK v0.53.x. Core vulnerability patterns (§1-9) covering non-determinism, ABCI, signers, validation, handlers, and ante security. For state/module patterns see [STATE_VULNERABILITY_PATTERNS.md](STATE_VULNERABILITY_PATTERNS.md); for advanced patterns see [ADVANCED_VULNERABILITY_PATTERNS.md](ADVANCED_VULNERABILITY_PATTERNS.md). Referenced by [SKILL.md](../SKILL.md) checklist items via `§` anchors.

### 1 NON-DETERMINISM

**Description**: Non-deterministic code in consensus causes different validators to produce different state roots, halting the chain. This is the most severe Cosmos vulnerability.

**Detection Patterns**:

#### Pattern 1: Map Iteration
```go
// VULNERABLE: Iterating over Go map (random order)
assets := make(map[string]sdk.Coin)
for assetID, coin := range assets {  // NON-DETERMINISTIC!
    keeper.ProcessAsset(ctx, assetID, coin)
}
```

#### Pattern 2: Platform-Dependent Types
```go
// VULNERABLE: Platform-dependent integer types
var amount int   // Size differs: 32-bit vs 64-bit systems
var price float64  // Float arithmetic is non-deterministic

// Serialization produces different bytes on different architectures
bz := someSerializer(amount)  // Different on 32-bit vs 64-bit!
```

#### Pattern 3: Goroutines and Concurrency
```go
// VULNERABLE: Goroutines have non-deterministic execution order
go processTransaction(tx1)
go processTransaction(tx2)
// Order of state updates is non-deterministic!
```

#### Pattern 4: Select Statements
```go
// VULNERABLE: Select with multiple ready channels
select {
case msg := <-ch1:  // Non-deterministic choice
    process(msg)
case msg := <-ch2:
    process(msg)
}
```

#### Pattern 5: Other Sources
```go
// VULNERABLE: Other non-determinism sources
rand.Intn(100)              // Random numbers
time.Now()                  // Local system time (use ctx.BlockTime())
&obj                        // Memory addresses
json.Marshal(map)           // Map serialization order
filepath.Walk()             // Filesystem traversal order
```

**What to Check**:
- [ ] NO `range` over maps in any consensus code
- [ ] NO `int`, `uint`, `float32`, `float64` types (use `int32`, `int64`, `math.Int`, `math.LegacyDec`)
- [ ] NO goroutines in message handlers or ABCI methods
- [ ] NO `select` statements with multiple channels
- [ ] NO `rand` package usage (use deterministic PRF)
- [ ] NO `time.Now()` (use `ctx.BlockTime()` or `ctx.BlockHeight()`)
- [ ] NO memory address usage (`&obj`, pointer comparisons)
- [ ] NO non-deterministic serialization

**Mitigation**:
```go
// SECURE: Deterministic iteration
// Option 1: Sort map keys
keys := make([]string, 0, len(assets))
for k := range assets {
    keys = append(keys, k)
}
sort.Strings(keys)  // Deterministic order
for _, k := range keys {
    keeper.ProcessAsset(ctx, k, assets[k])
}

// Option 2: Use ordered data structure (sdk.KVStore with ordered iteration)

// SECURE: Platform-independent types
var amount int64          // Explicit 64-bit
var amount math.Int       // Arbitrary precision integer (cosmossdk.io/math)
var price math.LegacyDec // Decimal type for consensus (cosmossdk.io/math)

// SECURE: Use block time, not system time
timestamp := ctx.BlockTime()  // Deterministic
height := ctx.BlockHeight()   // Deterministic
```

**Grep Patterns** (use Grep tool with `glob: "*.go"` in `x/`):
- `range.*map\[` — map iteration
- `go func` — goroutines
- `time\.Now\(\)` — system time
- `float64|float32` — floating point types
- `\bint\b|\buint\b` — platform-dependent integer types (exclude `int32`, `int64`, `uint32`, `uint64` results)

**References**: building-secure-contracts/not-so-smart-contracts/cosmos/non_determinism

---

### 2 ADDITIONAL NON-DETERMINISM SOURCES

**Description**: Beyond the core non-determinism patterns in §1, these additional sources can cause consensus failures.

**What to Check**:
- [ ] [`sort.Sort` is not stable](https://github.com/golang/go/issues/13884) — `sort.Stable` must be used when elements with equal keys must maintain insertion order. Same-order but different items may be sorted differently between Go versions
- [ ] Memory addresses — [even in non-committed storage, can cause indirect consensus failures](https://github.com/cosmos/cosmos-sdk/issues/11726#issuecomment-1108427164):
  - [Conversions to strings do not output addresses](https://gitlab.com/thorchain/thornode/-/merge_requests/2640) (structs not used directly in `fmt.Printf`)
  - [`%p` format string not used](https://facundo.dev/posts/2022/04/23/debug-stories-1-darwin-arm64-cosmos-sdk-builds-are-not-compatible-with-other-archs/)
  - [Pointers not compared](https://github.com/OffchainLabs/nitro/blob/affaf25ceb9ff53ed27e1072221a659ed63ca61d/linter/pointercheck/pointer.go)
- [ ] `filepath.Ext` is platform-dependent
- [ ] Struct padding [(unavoidable with cgo)](https://github.com/golang/go/wiki/cgo#struct-alignment-issues)
- [ ] [Platform-dependent build tags](https://pkg.go.dev/go/build#hdr-Build_Constraints) (`//go:build`, `// +build`)
- [ ] [`math/big.Random` non-deterministic even with const seed](https://github.com/golang/go/issues/42701)
- [ ] [Adding elements to map while iterating](https://go.dev/ref/spec#For_range)
- [ ] No leaks from GC or nondeterministic runtime internals ([Agoric example](https://github.com/Agoric/agoric-sdk/issues/5000))
- [ ] Parallel/concurrent tx execution (BlockSTM-style): modules must not have shared mutable state outside KV store
- [ ] Concurrency: goroutines, [`select` non-determinism](https://medium.com/@pedram.esmaeeli/a-pattern-for-overcoming-non-determinism-of-golang-select-statement-139dbe93db98), [synchronization with sleep](https://github.com/ligurio/semgrep-rules/blob/master/rules/non-determinism/go/synchronization-with-sleep.yaml). Use [race detector](https://go.dev/blog/race-detector)
- [ ] Floating point: read [here](https://gafferongames.com/post/floating_point_determinism/), [here](https://randomascii.wordpress.com/2013/07/16/floating-point-determinism/), [here](https://github.com/golang/go/issues/36536)

**Grep Patterns** (use Grep tool with `glob: "*.go"` in `x/`):
- `sort\.Sort\b` — unstable sort
- `"%p"` — memory address format
- `filepath\.Ext` — platform-dependent
- `go func` — goroutines
- `select \{` — non-deterministic select

---

### 3 SLOW ABCI METHODS

**Description**: Computationally expensive `BeginBlocker`, `EndBlocker`, or `PreBlocker` with unbounded loops can exceed block time limits, halting the chain. In SDK v0.50+, modules implement `appmodule.HasBeginBlocker` and `appmodule.HasEndBlocker` interfaces. `PreBlocker` is a new hook that runs before BeginBlock. Additionally, `appmodule.HasPrecommit` and `appmodule.HasPrepareCheckState` are lifecycle hooks that run after EndBlock and can also cause issues. Note: there are **two** EndBlock interfaces — `appmodule.HasEndBlocker` (returns `error`) and `module.HasABCIEndBlock` (returns `[]abci.ValidatorUpdate, error`, used by x/staking).

**Detection Patterns**:
```go
// VULNERABLE: Unbounded loop in EndBlocker (v0.50+ style)
func (am AppModule) EndBlock(ctx context.Context) error {
    sdkCtx := sdk.UnwrapSDKContext(ctx)
    // Iterates over ALL users - could be millions!
    am.keeper.IterateAllUsers(sdkCtx, func(user User) bool {
        reward := am.keeper.CalculateReward(sdkCtx, user)
        am.keeper.DistributeReward(sdkCtx, user, reward)
        return false
    })
    return nil
}

// VULNERABLE: Nested loops in BeginBlocker
func (am AppModule) BeginBlock(ctx context.Context) error {
    sdkCtx := sdk.UnwrapSDKContext(ctx)
    pools := am.keeper.GetAllPools(sdkCtx)        // 1000+ pools
    for _, pool := range pools {
        assets := am.keeper.GetPoolAssets(sdkCtx, pool.ID)  // 100+ assets each
        for _, asset := range assets {
            am.keeper.UpdatePrice(sdkCtx, pool, asset)  // Expensive calculation
        }
    }
    return nil
}

// VULNERABLE: Unbounded PreBlocker
func (am AppModule) PreBlock(ctx context.Context) error {
    // No limit on processing!
    am.keeper.ProcessAllPending(ctx)
    return nil
}
```

**What to Check**:
- [ ] `BeginBlock` has bounded computational complexity
- [ ] `EndBlock` has bounded computational complexity (both `HasEndBlocker` and `HasABCIEndBlock` variants)
- [ ] `PreBlock` (new in v0.50) has bounded computational complexity
- [ ] `Precommit` and `PrepareCheckState` hooks have bounded complexity
- [ ] NO nested loops over unbounded collections
- [ ] NO iterations over all users/pools/assets
- [ ] Batch operations have size limits
- [ ] Normal (non-malicious) transactions cannot exceed CometBFT's `max_bytes` or `max_gas` block limits — a single large but valid message (e.g., governance proposal with many updates, batch operation) that fills an entire block prevents other transactions from being included and can degrade liveness
- [ ] Stress tests with maximum expected data

**Mitigation**:
```go
// SECURE: Process limited batch per block (v0.50+ style)
func (am AppModule) EndBlock(ctx context.Context) error {
    sdkCtx := sdk.UnwrapSDKContext(ctx)
    maxProcessed := 100  // Process max 100 users per block

    iterator := am.keeper.GetUnprocessedUsers(sdkCtx)
    defer iterator.Close()

    count := 0
    for ; iterator.Valid() && count < maxProcessed; iterator.Next() {
        user := am.keeper.UnmarshalUser(iterator.Value())
        reward := am.keeper.CalculateReward(sdkCtx, user)
        am.keeper.DistributeReward(sdkCtx, user, reward)
        am.keeper.MarkProcessed(sdkCtx, user)
        count++
    }
    // Remaining users processed in subsequent blocks
    return nil
}
```

**Testing**:
```go
func BenchmarkEndBlocker(b *testing.B) {
    ctx := setupMaximumState()  // 1M users, 10K pools, etc.
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        am.EndBlock(ctx)
    }
    // Must complete in < 1 second
}
```

**References**: building-secure-contracts/not-so-smart-contracts/cosmos/abci_method_slow

---

### 4 ABCI METHODS PANIC

**Description**: Unexpected panics in `BeginBlock`, `EndBlock`, `PreBlock`, `Precommit`, or `PrepareCheckState` immediately stop the blockchain. Many Cosmos SDK types panic on invalid operations. In SDK v0.50+, math types have moved to `cosmossdk.io/math` and x/params is deprecated in favor of direct keeper param storage, but panic-prone operations remain. Note: `ValidateBasic()` is now deprecated and facultative (validation should happen in `msg_server.go`), making it more likely that invalid inputs reach panic-prone code paths.

**Detection Patterns**:

#### Pattern 1: Panic-Prone Coin Operations
```go
// VULNERABLE: NewCoins panics on invalid coins
func (am AppModule) EndBlock(ctx context.Context) error {
    sdkCtx := sdk.UnwrapSDKContext(ctx)
    // Panics if amount is negative or denom is invalid!
    coins := sdk.NewCoins(sdk.NewCoin(userDenom, math.NewInt(userAmount)))
    am.keeper.MintCoins(sdkCtx, coins)
    return nil
}

// VULNERABLE: Coin arithmetic panics
reward := sdk.NewCoin("uatom", math.NewInt(-100))  // PANIC: negative amount
```

#### Pattern 2: Math Operations
```go
// VULNERABLE: math.LegacyNewDecFromStr panics on invalid string
func (am AppModule) BeginBlock(ctx context.Context) error {
    price, err := math.LegacyNewDecFromStr(priceString)  // Returns error, but...
    // If using the non-error variant:
    price := math.LegacyMustNewDecFromStr(priceString)    // PANIC if invalid!
}

// VULNERABLE: Division by zero
ratio := amount.Quo(math.ZeroInt())  // PANIC!

// PITFALL: Using pointer to Dec is error-prone
var price *math.LegacyDec  // Unnecessary pointer — LegacyDec is already a single pointer internally
// Dec consists of a single big.Int pointer — pointer-to-pointer adds indirection with no performance benefit
// and introduces nil-pointer dereference risk
```

#### Pattern 3: Direct Param Storage (replaces SetParamSet)
```go
// VULNERABLE: Setting params without validation
// In v0.50+, modules store params directly in keeper state
func (k Keeper) SetParams(ctx context.Context, params Params) error {
    // If params are not validated before storage, invalid state can cause
    // panics later when params are read and used in calculations
    return k.Params.Set(ctx, params)
}
```

#### Pattern 4: Array Out of Bounds
```go
// VULNERABLE: No bounds checking
func processValidators(validators []Validator) {
    top := validators[0]  // PANIC if empty slice!
}
```

**What to Check**:
- [ ] All `sdk.NewCoins()`, `sdk.NewCoin()` calls validated
- [ ] All `math.LegacyNewDecFromStr()`, `math.NewIntFromString()` use error-returning variants
- [ ] No `Must*` variants (`LegacyMustNewDecFromStr`, `MustNewDecFromStr`) in consensus code
- [ ] Division operations check for zero divisor
- [ ] Params validated before storage (even with direct keeper storage)
- [ ] Array/slice access has bounds checking
- [ ] User input validated before use in panic-prone operations

**Mitigation**:
```go
// SECURE: Validate before panic-prone operations
func (am AppModule) EndBlock(ctx context.Context) error {
    sdkCtx := sdk.UnwrapSDKContext(ctx)

    if err := sdk.ValidateDenom(userDenom); err != nil {
        sdkCtx.Logger().Error("invalid denom", "error", err)
        return nil
    }
    if userAmount.IsNegative() {
        sdkCtx.Logger().Error("negative amount")
        return nil
    }

    coins := sdk.NewCoins(sdk.NewCoin(userDenom, userAmount))
    am.keeper.MintCoins(sdkCtx, coins)
    return nil
}

// SECURE: Use safe constructors
func (k Keeper) UpdatePrice(ctx context.Context, priceStr string) error {
    price, err := math.LegacyNewDecFromStr(priceStr)
    if err != nil {
        return err
    }

    if divisor.IsZero() {
        return errors.New("division by zero")
    }
    ratio := amount.Quo(divisor)
    return nil
}
```

**Grep Patterns** (use Grep tool with `glob: "*.go"` in `x/`):
- `MustNewDec|MustNewInt|LegacyMustNewDec` — panic-prone constructors
- `\.Quo|\.Div` — division operations (check for zero divisor)
- `math\.NewInt|math\.LegacyNewDec` — math constructors

**References**: building-secure-contracts/not-so-smart-contracts/cosmos/abci_method_panics

---

### 5 INCORRECT SIGNER ANNOTATION / MISMATCH

**Description**: In Cosmos SDK v0.47+, `GetSigners() []sdk.AccAddress` no longer exists on message types. `sdk.Msg` is now just `proto.Message`. Signers are declared via the `cosmos.msg.v1.signer` proto annotation and resolved automatically by `x/tx/signing`. The vulnerability now manifests as a mismatch between the proto-annotated signer field and the field actually used for authorization in `msg_server.go`.

**Detection Patterns**:

#### Pattern 1: Proto annotation vs msg_server mismatch
```protobuf
// In tx.proto
message MsgTransferOwnership {
  option (cosmos.msg.v1.signer) = "sender";  // "sender" is the verified signer

  string sender = 1;
  string new_owner = 2;
}
```

```go
// In msg_server.go - VULNERABLE: uses new_owner for authorization instead of sender
func (ms msgServer) TransferOwnership(ctx context.Context, msg *types.MsgTransferOwnership) (*types.MsgTransferOwnershipResponse, error) {
    // BUG: authorizing based on new_owner, not the verified sender!
    if !ms.IsAdmin(ctx, msg.NewOwner) {
        return nil, errors.ErrUnauthorized
    }
    // ...
}
```

#### Pattern 2: Wrong field annotated as signer
```protobuf
// VULNERABLE: signer annotation points to wrong field
message MsgExecute {
  option (cosmos.msg.v1.signer) = "authority";  // Should be "sender"

  string sender = 1;     // actual tx signer
  string authority = 2;  // module authority, not tx signer
}
```

#### Pattern 3: Multiple address fields with ambiguous signer
```protobuf
message MsgDelegate {
  option (cosmos.msg.v1.signer) = "delegator";

  string delegator = 1;
  string validator = 2;
  string beneficiary = 3;  // If handler uses this instead of delegator...
}
```

**What to Check**:
- [ ] `cosmos.msg.v1.signer` annotation field matches the field used for authorization in `msg_server.go`
- [ ] No multiple address fields where the wrong one is used for access control
- [ ] `msg_server.go` handler references `msg.Signer` (or whatever the annotated field is), not another address field
- [ ] Custom `GetSigners` overrides via `DefineCustomGetSigners` are correct

**Mitigation**:
```protobuf
// SECURE: Single clear signer field
message MsgExample {
  option (cosmos.msg.v1.signer) = "sender";
  string sender = 1;  // Only address field used for authorization
}
```

```go
// SECURE: msg_server uses the annotated signer field
func (ms msgServer) Example(ctx context.Context, msg *types.MsgExample) (*types.MsgExampleResponse, error) {
    // Use msg.Sender - the field declared in cosmos.msg.v1.signer
    if err := ms.authorize(ctx, msg.Sender); err != nil {
        return nil, err
    }
    // ...
}
```

**Testing**:
- Unit test with crafted messages where signer field != other address fields
- Verify only the annotated signer has authorization
- Integration test all message types

**References**: building-secure-contracts/not-so-smart-contracts/cosmos/incorrect_signer

---

### 6 MISSING MSG_SERVER VALIDATION (ValidateBasic Deprecation)

**Description**: In Cosmos SDK v0.50+, `ValidateBasic()` is deprecated and facultative — the SDK no longer requires messages to implement it. The recommended pattern is to validate inputs directly in `msg_server.go` handlers. However, if a module removes `ValidateBasic()` without adding equivalent validation in the handler, invalid messages reach state-changing code without checks.

**Detection Patterns**:
```go
// VULNERABLE: ValidateBasic removed but no validation in handler
// types/msgs.go - ValidateBasic was here but got removed during migration

// msg_server.go - No validation of inputs
func (ms msgServer) UpdateParams(ctx context.Context, msg *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) {
    // No check on msg.Authority
    // No check on msg.Params fields
    return &types.MsgUpdateParamsResponse{}, ms.Params.Set(ctx, msg.Params)
}
```

```go
// VULNERABLE: HasValidateBasic implemented but handler also needs validation
// types/msgs.go
func (msg *MsgTransfer) ValidateBasic() error {
    // Only checks basic format — NOT authorization or business logic
    return nil
}

// msg_server.go - Relies entirely on ValidateBasic, no handler-level checks
func (ms msgServer) Transfer(ctx context.Context, msg *types.MsgTransfer) (*types.MsgTransferResponse, error) {
    // Missing: check that msg.Amount is positive
    // Missing: check that sender has sufficient funds
    // Missing: authorization check against cosmos.msg.v1.signer
    return ms.executeTransfer(ctx, msg)
}
```

**What to Check**:
- [ ] Every `msg_server.go` handler validates all input fields (don't rely on ValidateBasic)
- [ ] Authority/signer checks are in the handler, not just in ValidateBasic
- [ ] Params are validated before `Params.Set()` calls
- [ ] Numeric inputs checked for negative/zero/overflow before use
- [ ] Address strings validated before conversion

**Mitigation**:
```go
// SECURE: Full validation in msg_server.go handler
func (ms msgServer) UpdateParams(ctx context.Context, msg *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) {
    if ms.authority != msg.Authority {
        return nil, errorsmod.Wrapf(govtypes.ErrInvalidSigner, "invalid authority; expected %s, got %s", ms.authority, msg.Authority)
    }
    if err := msg.Params.Validate(); err != nil {
        return nil, err
    }
    return &types.MsgUpdateParamsResponse{}, ms.Params.Set(ctx, msg.Params)
}
```

---

### 7 MISSING ERROR HANDLER

**Description**: Ignoring error return values from keeper methods (especially `bankKeeper.SendCoins`) allows invalid operations to silently succeed.

**Detection Patterns**:
```go
// VULNERABLE: Ignored error from SendCoins
func (k Keeper) Withdraw(ctx context.Context, user string, amount sdk.Coins) {
    // Error ignored! Withdrawal appears successful even if SendCoins fails
    k.bankKeeper.SendCoins(ctx, moduleAccount, user, amount)

    // Update state assuming withdrawal succeeded
    k.DecrementBalance(ctx, user, amount)
}

// VULNERABLE: Deferred error handling too late
func (k Keeper) ProcessBatch(ctx context.Context, txs []Transaction) {
    for _, tx := range txs {
        err := k.ProcessTransaction(ctx, tx)
        // Error not checked, continues processing!
    }
}
```

**What to Check**:
- [ ] ALL keeper method calls check error return values
- [ ] `bankKeeper.SendCoins()` errors always handled
- [ ] State updates only occur after successful operation
- [ ] Errors propagated to caller
- [ ] Use linters to detect ignored errors (errcheck)

**Mitigation**:
```go
// SECURE: Check all errors
func (k Keeper) Withdraw(ctx context.Context, user string, amount sdk.Coins) error {
    if err := k.bankKeeper.SendCoins(ctx, moduleAccount, user, amount); err != nil {
        return err
    }
    k.DecrementBalance(ctx, user, amount)
    return nil
}

// SECURE: Stop processing on first error
func (k Keeper) ProcessBatch(ctx context.Context, txs []Transaction) error {
    for _, tx := range txs {
        if err := k.ProcessTransaction(ctx, tx); err != nil {
            return fmt.Errorf("transaction failed: %w", err)
        }
    }
    return nil
}
```

**Linter Configuration**:
```yaml
# .golangci.yml
linters:
  enable:
    - errcheck
    - goerr113

linters-settings:
  errcheck:
    check-blank: true
    check-type-assertions: true
```

**References**: building-secure-contracts/not-so-smart-contracts/cosmos/missing_error_handler

---

### 8 UNREGISTERED MESSAGE HANDLER (Legacy Only)

**Description**: In Cosmos SDK v0.50+, all modules use protobuf service-based message handling (`msg_server.go`). The legacy `NewHandler`/`sdk.Handler` switch-case pattern no longer exists in the SDK. Messages are auto-registered when the module implements `RegisterServices`.

This pattern is **only relevant for chains still on SDK < v0.47** or chains that have not migrated legacy handlers.

**When This Still Applies**:
- Chains running Cosmos SDK < v0.47
- Custom modules that haven't migrated to protobuf services
- Forked SDK versions with legacy handler patterns

**Modern Equivalent Issue**:
Even with protobuf service registration, a related issue can occur:
```go
// msg_server.go - Message type defined in proto but method not implemented
type msgServer struct {
    Keeper
}

var _ types.MsgServer = msgServer{}

// If a method is declared in the proto service but the Go implementation
// is missing or returns a nil/empty response, the compiler will catch
// missing methods (unlike the legacy switch-case), but the implementation
// could still be a no-op stub.

func (ms msgServer) Withdraw(ctx context.Context, msg *types.MsgWithdraw) (*types.MsgWithdrawResponse, error) {
    // VULNERABLE: Stub implementation that does nothing
    return &types.MsgWithdrawResponse{}, nil
}
```

**What to Check**:
- [ ] All proto service RPC methods have real implementations in `msg_server.go`
- [ ] No stub/no-op implementations that accept messages without processing
- [ ] Integration tests call all message types and verify state changes
- [ ] `RegisterServices` is called in the module's `AppModule`

**References**: building-secure-contracts/not-so-smart-contracts/cosmos/message_handler_missing

---

### 9 ANTEHANDLER SECURITY

**Description**: AnteHandlers form the authentication and validation pipeline. Misconfigurations allow bypassing signature verification, fee checks, or authorization.

**What to Check**:
- [ ] `SetUpContext` is the first decorator in `ChainAnteDecorators`
- [ ] `SetPubKeyDecorator` before all signature verification decorators (`ValidateSigCountDecorator`, `SigVerificationDecorator`)
- [ ] Manual message routing is NOT performed — it [bypasses AnteHandlers](https://github.com/axelarnetwork/axelar-core/issues/1145)
- [ ] Custom AnteHandlers handle [nested/embedded messages](https://jumpcrypto.com/bypassing-ethermint-ante-handlers/) inside `x/authz MsgExec` or `x/group` proposal execution. SDK enforces `MaxUnpackAnyRecursionDepth = 10` ([GHSA-8wcc-m6j2-qxvm](https://github.com/cosmos/cosmos-sdk/security/advisories/GHSA-8wcc-m6j2-qxvm)) but semantic nesting (authz wrapping authz) must be handled by the ante handler itself
- [ ] `x/authz` grant creation reviewed for [privilege escalation (GHSA-4j93-fm92-rp4m)](https://github.com/cosmos/cosmos-sdk/security/advisories/GHSA-4j93-fm92-rp4m) — verify grants cannot circumvent blocked address restrictions; also check `x/feegrant`'s `GrantAllowance` and `x/auth/vesting`'s `CreatePeriodicVestingAccount` against `BlockedAddr`
- [ ] If `PostHandler` is set (`app.SetPostHandler`), its logic is reviewed — PostHandlers run after message execution and receive a `success` bool; errors can mask the original message result or cause unexpected state changes
- [ ] Unordered transactions (ADR-070): `SigVerificationDecorator` configured with `SigVerifyOptions` (timeout duration, gas cost)

**Key Vulnerability**: Nested message bypass. An attacker wraps a message inside `MsgExec` (authz) or a group proposal. If the custom ante handler only inspects the outer message, inner messages skip validation entirely.

```go
// VULNERABLE: AnteHandler only checks outer message type
func (d MyDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
    for _, msg := range tx.GetMsgs() {
        // Only checks msg type — does NOT recurse into MsgExec.Msgs!
        if _, ok := msg.(*types.MsgDangerous); ok {
            return ctx, errors.New("blocked")
        }
    }
    return next(ctx, tx, simulate)
}
```

```go
// SECURE: Recurse into nested messages
func (d MyDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
    for _, msg := range tx.GetMsgs() {
        if err := d.checkMsg(msg); err != nil {
            return ctx, err
        }
    }
    return next(ctx, tx, simulate)
}

func (d MyDecorator) checkMsg(msg sdk.Msg) error {
    if _, ok := msg.(*types.MsgDangerous); ok {
        return errors.New("blocked")
    }
    if exec, ok := msg.(*authz.MsgExec); ok {
        for _, inner := range exec.Msgs {
            var innerMsg sdk.Msg
            if err := d.cdc.UnpackAny(inner, &innerMsg); err != nil {
                return err
            }
            if err := d.checkMsg(innerMsg); err != nil {  // Recurse
                return err
            }
        }
    }
    return nil
}
```

**References**: [Jump Crypto — Bypassing Ethermint Ante Handlers](https://jumpcrypto.com/bypassing-ethermint-ante-handlers/)

---

### 10 IBC SECURITY

> IBC vulnerability patterns are covered separately in Step 4 of the scanning workflow. See [IBC_VULNERABILITY_PATTERNS.md](IBC_VULNERABILITY_PATTERNS.md). This section intentionally left as a placeholder to preserve numbering.
