Flow

Sablier
FoundryDeFi
20,000 USDC
View results
Submission Details
Severity: medium
Invalid

Withdraw allowed to be called by anyone, can ruin both parties financial planning

Description: 'SablierFlow::Withdraw' can be called by anyone as long as the 'to' address is set to the recipient. This is allowed according to the documentation, but it should be reconsidered. If a recipient and sender are using the streams as a payroll system, it can be problematic since a payroll is usually on a weekly, bi-weekly or monthly basis. Having withdraw callable by anyone would allow for a breach in a payroll agreement. While this function may be callable by anyone to allow automation, it could still be automated through an approved keeper address. Withdraws should only be callable by authorized users to allow for a seamless financial implementation by users.

Impact: The recipient can receive a withdraw at multiple random unexpected times since anyone can call the withdraw function.

Proof of Concept:

  1. Sender and recipient agree to use the streams as a payroll system.

  2. Recipient and sender expect to call a withdraw on a specific day every week.

  3. Unknown user calls the withdraw function at multiple unexpected times.

  4. Recipient and sender may be confused as to why they are receiving a withdraw at unexpected times if uninformed about the function being callable by anyone, and it is a breach in the agreement that the sender and recipient made.

Recommended Mitigation:

  • Add a check to see if the msg.sender is the recipient, sender, or approved third party.

function _withdraw(
uint256 streamId,
address to,
uint128 amount
)
internal
returns (uint128 withdrawnAmount, uint128 protocolFeeAmount)
{
// Check: the withdraw amount is not zero.
if (amount == 0) {
revert Errors.SablierFlow_WithdrawAmountZero(streamId);
}
// Check: the withdrawal address is not zero.
if (to == address(0)) {
revert Errors.SablierFlow_WithdrawToZeroAddress(streamId);
}
+ // Check: 'msg.sender' is approved caller
+ if (msg.sender != _ownerOf(streamId) && !_isCallerStreamRecipientOrApproved(streamId)) {
+ revert Errors.SablierFlow_Unauthorized({ streamId: streamId, caller: msg.sender });
+ }
// Check: `msg.sender` is neither the stream's recipient nor an approved third party, the withdrawal address
// must be the recipient.
if (to != _ownerOf(streamId) && !_isCallerStreamRecipientOrApproved(streamId)) {
revert Errors.SablierFlow_WithdrawalAddressNotRecipient({ streamId: streamId, caller: msg.sender, to: to });
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 8 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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