Skip to content

Commit 3cdc810

Browse files
feat: add recursive check for nested authz exec messages and enforce … (#2420)
* feat: add recursive check for nested authz exec messages and enforce max depth limit * changelog * docs(CHANGELOG): move log entry to correct position --------- Co-authored-by: Unique Divine <51418232+Unique-Divine@users.noreply.github.com>
1 parent b1d1c22 commit 3cdc810

File tree

3 files changed

+77
-12
lines changed

3 files changed

+77
-12
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ See https://github.com/dangoslen/changelog-enforcer.
4949

5050
- [#2418](https://github.com/NibiruChain/nibiru/pull/2418) - fix(evmstate/test): stabilize trace tx tests with deterministic ERC20 transfer recipient
5151
- [#2419](https://github.com/NibiruChain/nibiru/pull/2419) - ci: simplify Go caching in CI to prevent file collisions
52+
- [#2420](https://github.com/NibiruChain/nibiru/pull/2420) - feat: add recursive check for nested authz exec messages and enforce max depth limit
5253

5354
## [v2.8.0](https://github.com/NibiruChain/nibiru/releases/tag/v2.8.0) - 2025-10-28
5455

app/ante/auth_guard_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,45 @@ func (s *Suite) TestAnteDecoratorAuthzGuard() {
9797
},
9898
wantErr: "ExtensionOptionsEthereumTx",
9999
},
100+
{
101+
name: "sad: nested authz exec with evm message inside",
102+
txMsg: func() sdk.Msg {
103+
// inner exec contains an EVM message
104+
inner := authz.NewMsgExec(
105+
sdk.AccAddress("nibiuser"),
106+
[]sdk.Msg{
107+
&evm.MsgEthereumTx{},
108+
},
109+
)
110+
// outer exec wraps inner
111+
outer := authz.NewMsgExec(
112+
sdk.AccAddress("nibiuser"),
113+
[]sdk.Msg{&inner},
114+
)
115+
return &outer
116+
},
117+
wantErr: "ExtensionOptionsEthereumTx",
118+
},
119+
{
120+
name: "sad: nested authz exec exceeds max depth",
121+
txMsg: func() sdk.Msg {
122+
// Build triple-nested exec without EVM msgs to trigger depth check only
123+
lvl3 := authz.NewMsgExec(
124+
sdk.AccAddress("nibiuser"),
125+
[]sdk.Msg{&banktypes.MsgSend{}},
126+
)
127+
lvl2 := authz.NewMsgExec(
128+
sdk.AccAddress("nibiuser"),
129+
[]sdk.Msg{&lvl3},
130+
)
131+
lvl1 := authz.NewMsgExec(
132+
sdk.AccAddress("nibiuser"),
133+
[]sdk.Msg{&lvl2},
134+
)
135+
return &lvl1
136+
},
137+
wantErr: "exceeded max nested message depth: 2",
138+
},
100139
{
101140
name: "happy: authz exec without evm messages",
102141
txMsg: func() sdk.Msg {

app/ante/authz_guard.go

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
package ante
33

44
import (
5+
"fmt"
6+
57
sdkioerrors "cosmossdk.io/errors"
68
sdk "github.com/cosmos/cosmos-sdk/types"
79
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
@@ -10,6 +12,8 @@ import (
1012
"github.com/NibiruChain/nibiru/v2/x/evm"
1113
)
1214

15+
const maxNestedMsgs = 2
16+
1317
// AnteDecAuthzGuard filters autz messages
1418
type AnteDecAuthzGuard struct{}
1519

@@ -46,22 +50,43 @@ func (anteDec AnteDecAuthzGuard) AnteHandle(
4650
}
4751
// Also reject MsgEthereumTx in exec
4852
if msgExec, ok := msg.(*authz.MsgExec); ok {
49-
msgsInExec, err := msgExec.GetMessages()
50-
if err != nil {
51-
return ctx, sdkioerrors.Wrapf(
53+
if err := anteDec.checkMsgExecRecursively(msgExec, 0, maxNestedMsgs); err != nil {
54+
return ctx, sdkerrors.Wrapf(
5255
sdkerrors.ErrInvalidType,
53-
"failed getting exec messages %s", err,
56+
err.Error(),
5457
)
5558
}
56-
for _, msgInExec := range msgsInExec {
57-
if _, ok := msgInExec.(*evm.MsgEthereumTx); ok {
58-
return ctx, sdkioerrors.Wrapf(
59-
sdkerrors.ErrInvalidType,
60-
"MsgEthereumTx needs to be contained within a tx with 'ExtensionOptionsEthereumTx' option",
61-
)
62-
}
63-
}
6459
}
6560
}
6661
return next(ctx, tx, simulate)
6762
}
63+
64+
func (anteDec AnteDecAuthzGuard) checkMsgExecRecursively(msgExec *authz.MsgExec, depth int, maxDepth int) error {
65+
if depth >= maxDepth {
66+
return fmt.Errorf("exceeded max nested message depth: %d", maxDepth)
67+
}
68+
69+
msgsInExec, err := msgExec.GetMessages()
70+
if err != nil {
71+
return sdkerrors.Wrapf(
72+
sdkerrors.ErrInvalidType,
73+
"failed getting exec messages %s", err,
74+
)
75+
}
76+
77+
for _, msg := range msgsInExec {
78+
if _, ok := msg.(*evm.MsgEthereumTx); ok {
79+
return sdkioerrors.Wrapf(
80+
sdkerrors.ErrInvalidType,
81+
"MsgEthereumTx needs to be contained within a tx with 'ExtensionOptionsEthereumTx' option",
82+
)
83+
}
84+
if nestedExec, ok := msg.(*authz.MsgExec); ok {
85+
if err := anteDec.checkMsgExecRecursively(nestedExec, depth+1, maxDepth); err != nil {
86+
return err
87+
}
88+
}
89+
}
90+
91+
return nil
92+
}

0 commit comments

Comments
 (0)