From 45acb3d73f8fcbd6c7d10cc996ed0b7bf12f7be4 Mon Sep 17 00:00:00 2001 From: Anton Filonenko Date: Wed, 13 May 2026 13:42:26 +0300 Subject: [PATCH 1/2] MF-L09: fix(nitronode): validate parsed app session nonce CreateAppSession previously rejected the raw nonce string "0" only, but strconv.ParseUint accepts zero-padded inputs ("00", "000", ...) and yields 0. The empty-string branch was also unreachable because unmapAppDefinitionV1 errors out earlier on an unparseable nonce. Switch the check to the parsed appDef.Nonce value and extend TestCreateAppSession_ZeroNonce to cover the zero-padded bypass. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../api/app_session_v1/create_app_session.go | 2 +- .../app_session_v1/create_app_session_test.go | 112 +++++++++--------- 2 files changed, 58 insertions(+), 56 deletions(-) diff --git a/nitronode/api/app_session_v1/create_app_session.go b/nitronode/api/app_session_v1/create_app_session.go index ecb3879b1..daa410aad 100644 --- a/nitronode/api/app_session_v1/create_app_session.go +++ b/nitronode/api/app_session_v1/create_app_session.go @@ -59,7 +59,7 @@ func (h *Handler) CreateAppSession(c *rpc.Context) { "nonce", reqPayload.Definition.Nonce) // Validate nonce - if reqPayload.Definition.Nonce == "" || reqPayload.Definition.Nonce == "0" { + if appDef.Nonce == 0 { c.Fail(nil, "nonce is zero or not provided") return } diff --git a/nitronode/api/app_session_v1/create_app_session_test.go b/nitronode/api/app_session_v1/create_app_session_test.go index 7bd3186e2..db4681c14 100644 --- a/nitronode/api/app_session_v1/create_app_session_test.go +++ b/nitronode/api/app_session_v1/create_app_session_test.go @@ -223,68 +223,70 @@ func TestCreateAppSession_QuorumWithMultipleSignatures(t *testing.T) { } func TestCreateAppSession_ZeroNonce(t *testing.T) { - // Setup - mockStore := new(MockStore) - - storeTxProvider := func(fn StoreTxHandler) error { - return fn(mockStore) - } - - mockSigner := NewMockChannelSigner() - mockAssetStore := new(MockAssetStore) - mockStatePacker := new(MockStatePacker) - - handler := NewHandler( - storeTxProvider, - mockAssetStore, - &MockActionGateway{}, - mockSigner, - core.NewStateAdvancerV1(mockAssetStore), - mockStatePacker, - "0xnode", - true, - metrics.NewNoopRuntimeMetricExporter(), - 32, 1024, 256, 16, 100, - ) - - // Test data - participant1 := "0x1111111111111111111111111111111111111111" - - reqPayload := rpc.AppSessionsV1CreateAppSessionRequest{ - Definition: rpc.AppDefinitionV1{ - Application: "test-app", - Participants: []rpc.AppParticipantV1{ - { - WalletAddress: participant1, - SignatureWeight: 1, + // Zero-padded values must also be rejected: strconv.ParseUint accepts "00", + // "000", etc. and yields 0, which used to bypass the raw-string "0" check. + cases := []string{"0", "00", "000"} + for _, nonce := range cases { + t.Run("nonce="+nonce, func(t *testing.T) { + mockStore := new(MockStore) + + storeTxProvider := func(fn StoreTxHandler) error { + return fn(mockStore) + } + + mockSigner := NewMockChannelSigner() + mockAssetStore := new(MockAssetStore) + mockStatePacker := new(MockStatePacker) + + handler := NewHandler( + storeTxProvider, + mockAssetStore, + &MockActionGateway{}, + mockSigner, + core.NewStateAdvancerV1(mockAssetStore), + mockStatePacker, + "0xnode", + true, + metrics.NewNoopRuntimeMetricExporter(), + 32, 1024, 256, 16, 100, + ) + + participant1 := "0x1111111111111111111111111111111111111111" + + reqPayload := rpc.AppSessionsV1CreateAppSessionRequest{ + Definition: rpc.AppDefinitionV1{ + Application: "test-app", + Participants: []rpc.AppParticipantV1{ + { + WalletAddress: participant1, + SignatureWeight: 1, + }, + }, + Quorum: 1, + Nonce: nonce, }, - }, - Quorum: 1, - Nonce: "0", // Zero nonce - invalid - }, - QuorumSigs: []string{"0x1234567890abcdef"}, - } + QuorumSigs: []string{"0x1234567890abcdef"}, + } - // Create RPC context - payload, err := rpc.NewPayload(reqPayload) - require.NoError(t, err) + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) - ctx := &rpc.Context{ - Context: context.Background(), - Request: rpc.NewRequest(1, string(rpc.AppSessionsV1CreateAppSessionMethod), payload), - } + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, string(rpc.AppSessionsV1CreateAppSessionMethod), payload), + } - handler.CreateAppSession(ctx) + handler.CreateAppSession(ctx) - assert.NotNil(t, ctx.Response) + assert.NotNil(t, ctx.Response) - // Verify response contains error about nonce - err = ctx.Response.Error() - require.Error(t, err) - assert.Contains(t, err.Error(), "nonce") + err = ctx.Response.Error() + require.Error(t, err) + assert.Contains(t, err.Error(), "nonce") - // Verify no mocks were called since we fail early - mockStore.AssertExpectations(t) + mockStore.AssertExpectations(t) + }) + } } func TestCreateAppSession_QuorumExceedsTotalWeights(t *testing.T) { From 37219cc69347c83a0e6284f3c18fc2b61fcb8539 Mon Sep 17 00:00:00 2001 From: Anton Filonenko Date: Wed, 13 May 2026 18:04:59 +0300 Subject: [PATCH 2/2] fix(nitronode): align app session nonce error message with sibling handler Empty/non-numeric nonces are already rejected earlier in unmapAppDefinitionV1 with a parse error, so this check only catches integer 0. Match channel_v1/request_creation.go wording. Co-Authored-By: Claude Opus 4.7 (1M context) --- nitronode/api/app_session_v1/create_app_session.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nitronode/api/app_session_v1/create_app_session.go b/nitronode/api/app_session_v1/create_app_session.go index daa410aad..27a65af63 100644 --- a/nitronode/api/app_session_v1/create_app_session.go +++ b/nitronode/api/app_session_v1/create_app_session.go @@ -60,7 +60,7 @@ func (h *Handler) CreateAppSession(c *rpc.Context) { // Validate nonce if appDef.Nonce == 0 { - c.Fail(nil, "nonce is zero or not provided") + c.Fail(nil, "nonce must be non-zero") return }