MultiSig Timelock

First Flight #55
Beginner FriendlyWallet
100 EXP
Submission Details
Impact: high
Likelihood: high

Only the owner can propose a transaction, SIGNER_ROLE cannot

Author Revealed upon completion

Root + Impact

https://github.com/CodeHawks-Contests/2025-12-multisig-timelock/blob/main/src/MultiSigTimelock.sol#L253

Description

Any SIGNER should be able to propose a transaction (as stated in the project's description). But here, only the owner is able to propose a transaction.

function proposeTransaction(address to, uint256 value, bytes calldata data)
external
nonReentrant
noneZeroAddress(to)
onlyOwner // <@ Here is the root cause
returns (uint256)
{
return _proposeTransaction(to, value, data);
}

Risk

Likelihood:

  • Everytime a proposal is submitted

Impact:

  • Proposal can only be submitted by the owner

  • The signers cannot submit a proposal, which is not fair

Proof of Concept

/////////////////////////////////////
/// PROPOSE TRANSACTION TESTS /////
/////////////////////////////////////
function testSignersCannotProposeTransaction() public {
// ARRANGE
vm.deal(SIGNER_TWO, OWNER_BALANCE_ONE);
vm.deal(SIGNER_THREE, OWNER_BALANCE_ONE);
// ACT & ASSERT
vm.prank(SIGNER_TWO);
uint256 txnId = multiSigTimelock.proposeTransaction(SPENDER_ONE, OWNER_BALANCE_ONE, hex"");
console2.log("This is the first transaction ID", txnId);
assertNotEq(txnId, 0);
vm.prank(SIGNER_THREE);
uint256 txnIdTwo = multiSigTimelock.proposeTransaction(SPENDER_ONE, OWNER_BALANCE_ONE, hex"");
console2.log("This is the second transaction ID", txnIdTwo);
assertNotEq(txnIdTwo, 1);
}

Recommended Mitigation

function proposeTransaction(address to, uint256 value, bytes calldata data)
external
nonReentrant
noneZeroAddress(to)
- onlyOwner
+ onlyRole(SIGNING_ROLE)
returns (uint256)
{
return _proposeTransaction(to, value, data);
}

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!