bcnmy/nexus

[BUG]: Constraint Validation Bypass in Nexus ComposableExecution

オープン

#304 opened on 2026/08/17

 (0 件のコメント) (0 件のリアクション) (0 人の担当者)Solidity (31 件のフォーク)auto 404
bughelp wanted

Repository metrics

Stars
 (56 個のスター)
PR merge metrics
 (平均マージ 1h 19m) (30d で 1 merged PR)

説明

Summary

ComposableExecutionLib._validateConstraints() can incorrectly validate constraints against the wrong memory word, allowing constraints after the first index to be bypassed. For single-value fetchers such as BALANCE and single-word RAW_BYTES, the returned value contains only one logical 32-byte word. However, the current implementation uses the constraint index as a word offset: returnValue := mload(add(rawValue, add(0x20, mul(i, 0x20)))) As a result, Constraint[1], Constraint[2], etc. are not checked against the actual fetched value and may pass or fail depending on unrelated memory contents. This can cause execution to continue with values that violate the declared constraints.

Steps to Reproduce

Clone the Nexus repository and run the attached Foundry PoC:

PoC_ConstraintOOBBypass.t.sol

The PoC uses:

Real Nexus account flow Real EntryPoint v0.7 Real ComposableExecutionLib Real BALANCE and RAW_BYTES fetchers

No vulnerable logic is mocked.

Test Case 1 — BALANCE constraint bypass

Create a composable execution with:

Fetched value: BOB_ACCOUNT balance = 100 ether

Constraints:

Constraint[0]: GTE(0)

Constraint[1]: LTE(10 ether)

The second constraint declares that the balance must not exceed 10 ether.

Execute:

Nexus.executeComposable()

Observed result:

DummyContract.setFoo(100 ether)

The execution succeeds even though:

100 ether > 10 ether

and the LTE(10 ether) constraint should have reverted.

Test Case 2 — RAW_BYTES constraint bypass

Create a composable execution with:

Input value: 999999999

Constraints:

Constraint[0]: GTE(0)

Constraint[1]: LTE(1000)

Expected behavior:

ConstraintNotMet revert

Actual behavior:

DummyContract.setFoo(999999999)

The upper-bound constraint is bypassed and execution continues.

Expected vs. Actual Behavior

Expected vs. Actual Behavior Expected Behavior

Every constraint attached to an InputParam should validate the same fetched value.

Example:

Constraint[0] -> value Constraint[1] -> value Constraint[2] -> value

For:

value = 100 ether constraint = LTE(10 ether)

execution should revert.

Actual Behavior

The implementation treats the constraint index as a memory-word index:

Constraint[0] -> rawValue word 0 Constraint[1] -> rawValue word 1 Constraint[2] -> rawValue word 2

For single-word values, words after index 0 are outside the logical encoded value.

Therefore:

Constraint[1]

does not validate the original fetched value.

This allows invalid values to satisfy constraints and execution proceeds.

Attachment:

forge-test-output.txt

PoC_ConstraintOOBBypass.t.sol.txt

Environment

Repository: bcnmy/nexus Component: ComposableExecutionLib (package: @biconomy/composability) Version: v1.1.0 Framework: Foundry Solidity: 0.8.x Test framework: forge test EntryPoint: v0.7

Code of Conduct

  • I agree to follow this project's Code of Conduct.

コントリビューターガイド