MultiSig Timelock

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

[H-3] Propose Restricted

Author Revealed upon completion

Propose Restricted Vulnerability. Transaction proposal restricted to owner , contradicting project description allowing any signer to propose and limiting functionality to a single point.Creates workflow bottlenecks and single-point failure risks, potentially halting operations if the owner is unavailable or uncooperative, undermining distributed governance.

Description

  • The proposeTransaction function is meant to enable the initiation of new transactions in the multi-signature workflow, with the project description stating that any signer holding the SIGNING_ROLE can propose, tying permission to the role for decentralized participation.

  • However, the function is gated by the onlyOwner modifier, restricting proposals exclusively to the contract owner and preventing other signers from initiating transactions, which deviates from the intended design and introduces a centralized bottleneck.

function proposeTransaction(address to, uint256 value, bytes calldata data)
external
nonReentrant
noneZeroAddress(to)
onlyOwner // @> Restricts to owner only, not allowing other signers
returns (uint256)
{
return _proposeTransaction(to, value, data);
}

Risk

Likelihood:

  • During normal operations in a team setting where non-owner signers attempt to initiate transactions as per the documented role permissions.

  • In scenarios where the owner is offline, compromised, or uncooperative, blocking all new proposals from proceeding.

Impact:

  • Operational bottlenecks that prevent timely transaction initiations, leading to delays in fund movements or governance actions in time-sensitive situations.

  • Undermines the multi-signature model's decentralized intent, creating a single-point failure that could result in indirect financial losses from missed opportunities or stalled protocols.

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "./MultiSigTimelock.sol"; // Assume this is your contract file
import "forge-std/Test.sol"; // For vm.prank in Foundry
contract ProposeRestrictedPoC is Test {
MultiSigTimelock public wallet;
address public signer2 = address(0x2222222222222222222222222222222222222222);
function setUp() external {
wallet = new MultiSigTimelock(); // Deployer (this) is owner and signer1
vm.deal(address(wallet), 10 ether); // Fund wallet
wallet.grantSigningRole(signer2); // Add non-owner signer
}
function exploit() external {
// Attempt propose as non-owner signer2
vm.prank(signer2);
vm.expectRevert("Ownable: caller is not the owner"); // Reverts due to onlyOwner
wallet.proposeTransaction(address(0x4444444444444444444444444444444444444444), 1 ether, "");
}
}
// In Foundry: forge test --match-test exploit
// Expected: Proposal reverts for non-owner signer, despite having SIGNING_ROLE.

Recommended Mitigation

- onlyOwner
+ onlyRole(SIGNING_ROLE)

Support

FAQs

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

Give us feedback!