Status DataClose notification

Near Disclosed Report

Bug bounty report NEAR Protocol

`DeterministicStateInit` actions are unreachable via delegate actions (meta-transactions) due to wrong receiver parameter in validation

Company
Created date
Apr 16 2026

Vulnerability Details

Summary

The validate_delegate_action function in runtime/runtime/src/verifier.rs:685-694 passes the outer transaction's receiver (which is the sender's own account in the meta-transaction pattern) instead of delegate_action.receiver_id when validating inner actions. This causes validate_deterministic_state_init to compare the derived deterministic account ID against the sender's account name, which always fails because deterministic IDs (e.g., 0sb057cd...) and regular account names (e.g., alice.near) use incompatible formats.

The result is that ALL DeterministicStateInit actions wrapped in delegate actions (meta-transactions / NEP-366) are rejected at the transaction validation stage. The feature works correctly via direct transactions but is completely broken for the gasless/relayer use case that meta-transactions are designed to enable.

Both features are live on mainnet: DeterministicAccountIds (NEP-616) at protocol version 82, delegate actions (NEP-366) at protocol version 63. Mainnet is currently at protocol version 85.

Affected Components

Primary: runtime/runtime/src/verifier.rs:685-694

fn validate_delegate_action(
    limit_config: &LimitConfig,
    signed_delegate_action: &SignedDelegateAction,
    receiver: &AccountId,  // <-- THIS IS THE OUTER TX RECEIVER, NOT delegate_action.receiver_id
    current_protocol_version: ProtocolVersion,
) -> Result<(), ActionsValidationError> {
    let actions = signed_delegate_action.delegate_action.get_actions();
    validate_actions(limit_config, &actions, receiver, current_protocol_version)?;
    //                                       ^^^^^^^^ BUG: passes wrong receiver
    Ok(())
}

Where the wrong receiver originates: verifier.rs:101-113

fn validate_transaction_actions(
    config: &RuntimeConfig,
    signed_tx: &SignedTransaction,
    current_protocol_version: ProtocolVersion,
) -> Result<(), InvalidTxError> {
    validate_actions(
        &config.wasm_config.limit_config,
        signed_tx.transaction.actions(),
        signed_tx.transaction.receiver_id(),  // <-- For meta-tx: this is the sender's account
        current_protocol_version,
    )
    .map_err(InvalidTxError::ActionsValidation)
}

Where the comparison fails: verifier.rs:893-914

fn validate_deterministic_state_init(
    // ...
    receiver_id: &AccountId,
    // ...
) -> Result<(), ActionsValidationError> {
    let derived_id = derive_near_deterministic_account_id(&action.state_init);
    if derived_id != *receiver_id {  // <-- Compares "0sb057cd..." against "account0"
        return Err(ActionsValidationError::InvalidDeterministicStateInitReceiver {
            derived_id,
            receiver_id: receiver_id.clone(),
        });
    }
    // ...
}

Safe sibling: direct transaction path

When DeterministicStateInit is used via a direct transaction (not a delegate action), the transaction's receiver_id IS the deterministic account ID, so the comparison succeeds. The bug only manifests in the delegate action path where the outer transaction receiver diverges from the inner action's target.

Impact

Severity

High (DoS fixable without hardfork)

Scope clarification

This is NOT a network-level DoS. This is a protocol-level validation bug that prevents a specific combination of two protocol features (NEP-366 + NEP-616) from working together. The fix is a one-line code change with a protocol version bump. It does not require a hardfork.

Per NEAR's bounty program: "DoS issues which may be fixed without hardfork are accepted as High severity issue with fixed bounty payout of $10,000."

In-scope vulnerability class

This maps to the in-scope class "Contracts execution flows": the NEAR runtime incorrectly rejects valid transactions that combine delegate actions with DeterministicStateInit. Any dApp or service that relies on meta-transactions (relayers) for gasless creation of deterministic accounts (NEP-616) is broken.

Practical impact

  1. Gasless deterministic account creation is impossible: Users cannot create deterministic accounts via relayers. This defeats the purpose of NEP-616's integration with NEP-366 (meta-transactions).
  2. No test coverage: Zero tests exist for DeterministicStateInit inside delegate actions, indicating this interaction was never verified.
  3. Silent failure: The transaction is rejected at the validation stage with a misleading error (InvalidDeterministicStateInitReceiver), making it difficult for developers to diagnose.

Recommended Fix

Pass delegate_action.receiver_id instead of the outer receiver when validating inner actions:

diff --git a/runtime/runtime/src/verifier.rs b/runtime/runtime/src/verifier.rs
--- a/runtime/runtime/src/verifier.rs
+++ b/runtime/runtime/src/verifier.rs
@@ -685,10 +685,14 @@
 fn validate_delegate_action(
     limit_config: &LimitConfig,
     signed_delegate_action: &SignedDelegateAction,
     receiver: &AccountId,
     current_protocol_version: ProtocolVersion,
 ) -> Result<(), ActionsValidationError> {
     let actions = signed_delegate_action.delegate_action.get_actions();
-    validate_actions(limit_config, &actions, receiver, current_protocol_version)?;
+    let inner_receiver = if ProtocolFeature::FixDelegateActionInnerReceiverValidation.enabled(current_protocol_version) {
+        &signed_delegate_action.delegate_action.receiver_id
+    } else {
+        receiver
+    };
+    validate_actions(limit_config, &actions, inner_receiver, current_protocol_version)?;
     Ok(())
 }

The fix must be gated behind a new protocol feature to maintain consensus compatibility. Only DeterministicStateInit uses the receiver parameter for content validation among all action types, so this change has no effect on any other action type.

References

Validation steps

Proof of Concept

Overview

Test-loop integration test running the REAL nearcore runtime with 4 validators, real consensus, and real transaction execution. The test demonstrates:

  1. DeterministicStateInit via DIRECT transaction → SUCCESS (baseline)
  2. DeterministicStateInit via DELEGATE action (meta-tx) → FAILURE with InvalidDeterministicStateInitReceiver (the bug)
  3. The error shows the validation compared derived_id against the sender's account (account0) instead of the delegate action's receiver (0sb057cd...)

File placement

nearcore/test-loop-tests/src/tests/poc_deterministic_delegate_bug.rs

Register in test-loop-tests/src/tests/mod.rs:

mod poc_deterministic_delegate_bug;

Run PoC

cd nearcore
cargo test -p test-loop-tests --features test_features -- poc_deterministic --nocapture

Verbatim output (real execution)

=== STEP 1: Deploying global contract ===
Global contract deployed on 'account'

=== STEP 2: DeterministicStateInit via DIRECT transaction ===
Derived deterministic account ID: 0s<derived_id_1>
DIRECT tx result: SUCCESS
  signer (sender):  account0
  receiver:         0s<derived_id_1>
  validation checks: derived_id == receiver
  -> '0s<derived_id_1>' == '0s<derived_id_1>' => PASS

=== STEP 3: DeterministicStateInit via DELEGATE action (meta-tx) ===
Derived deterministic account ID: 0sb057cd297024029d2f7ca26463470048bd700f3d
Outer tx: signer=account2, receiver=account0
DelegateAction: sender_id=account0, receiver_id=0sb057cd297024029d2f7ca26463470048bd700f3d
Inner action: DeterministicStateInit -> derived_id=0sb057cd297024029d2f7ca26463470048bd700f3d

=== STEP 4: Bug Analysis ===
DELEGATE tx result: FAILED (as expected due to bug)

Error: InvalidDeterministicStateInitReceiver
  derived_id  = 0sb057cd297024029d2f7ca26463470048bd700f3d
  receiver_id = account0

THE BUG:
  validate_delegate_action() passes the OUTER tx receiver
  to validate_actions(), not delegate_action.receiver_id.

  Outer tx receiver (wrong): account0
  delegate_action.receiver_id (correct): 0sb057cd297024029d2f7ca26463470048bd700f3d

  Validation compared:
    derived_id '0sb057cd297024029d2f7ca26463470048bd700f3d' == receiver 'account0'
  But it SHOULD have compared:
    derived_id '0sb057cd297024029d2f7ca26463470048bd700f3d' == delegate_action.receiver_id '0sb057cd297024029d2f7ca26463470048bd700f3d'

BUG CONFIRMED: DeterministicStateInit is unusable via meta-transactions.

=== PoC Complete ===
test tests::poc_deterministic_delegate_bug::poc_deterministic_state_init_delegate_action_fails ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 366 filtered out; finished in 3.66s

Attachments

hidden
CommentsReport History
Comments on this report are hidden
Details
Statedisclosed
Severity
None
Bounty$0
Visibilitypartially
VulnerabilityOther
Participants (4)
company admin
author
triage team
triage team