The proposeTransaction function incorrectly uses onlyOwner instead of onlyRole(SIGNING_ROLE), contradicting the specification that any signer should be able to propose transactions (README.md:50).
This creates centralization where only the owner can propose, while other signer functions (confirmTransaction, revokeConfirmation, executeTransaction) correctly use onlyRole(SIGNING_ROLE).
Comparison with other signer functions:
confirmTransaction correctly uses onlyRole(SIGNING_ROLE) (line 268)
revokeConfirmation correctly uses onlyRole(SIGNING_ROLE) (line 282)
executeTransaction correctly uses onlyRole(SIGNING_ROLE) (line 294)
This inconsistency confirms that proposeTransaction should also use onlyRole(SIGNING_ROLE).
Likelihood:
High: Occurs whenever a non-owner signer attempts to propose a transaction.
Impact:
Centralization risk: Single point of failure for proposals
Functionality mismatch: Non-owner signers cannot propose despite having SIGNING_ROLE
Steps to Reproduce:
Deploy MultiSigTimelock contract
Grant SIGNING_ROLE to a non-owner address (e.g., signerTwo)
Verify the address has SIGNING_ROLE using hasRole(SIGNING_ROLE, signerTwo)
Attempt to call proposeTransaction as signerTwo
Transaction reverts due to onlyOwner modifier
Only the owner can successfully propose transactions
Change the proposeTransaction function to use onlyRole(SIGNING_ROLE) instead of onlyOwner:
This change:
Aligns with the contest specification (README.md:50)
Matches the pattern used in other signer functions (confirmTransaction, revokeConfirmation, executeTransaction)
Restores the intended role-based access control where all signers have equal proposal capabilities
Maintains security since only addresses with SIGNING_ROLE can propose (which is managed by the owner)
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.