[BUG]: Constraint Validation Bypass in Nexus ComposableExecution
#304 opened on 2026/08/17
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:
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.