Santa's List

AI First Flight #3
Beginner FriendlyFoundry
EXP
View results
Submission Details
Impact: medium
Likelihood: medium
Invalid

Griefing via spam likes and MultiSig blocking -- no dispute resolution

Description

  • The protocol deploys a 2-of-2 MultiSigWallet for each matched pair, requiring both parties to approve transactions before funds can be withdrawn. This is intended for cooperative custody.

  • Any user can force themselves into another user's matches by liking them and waiting for reciprocation. Once matched, the attacker co-controls the MultiSig and can permanently block all transactions by refusing to approve. The 2-of-2 multisig has no timeout, no fallback recovery, no dispute resolution, and no transaction cancellation mechanism. Funds are permanently locked if one party is uncooperative.

// LikeRegistry.sol -- anyone can like anyone (with profile)
function likeUser(address liked) external payable {
// @> No mechanism to reject or block incoming likes
// @> Match is automatic on mutual like -- no confirmation step
matchRewards(liked, msg.sender); // @> deploys MultiSig with attacker as co-owner
}
// MultiSig.sol -- 2-of-2 with no escape hatch
function executeTransaction(uint256 _txId) external onlyOwners {
require(txn.approvedByOwner1 && txn.approvedByOwner2, "Not enough approvals");
// @> If one owner never approves, funds are locked forever
// @> No timeout, no dispute resolution, no emergency withdrawal
}

Risk

Likelihood:

  • This occurs when one member of a matched pair becomes uncooperative, loses their private key, or is intentionally malicious

  • An attacker can cheaply spam-like many users (1 ETH per target) to force matches and then refuse to cooperate on any MultiSig

Impact:

  • ETH deposited into the MultiSig is permanently locked with no recovery mechanism

  • Users have no way to reject unwanted matches or remove a malicious co-owner from the MultiSig

Proof of Concept

function testMultiSigGriefing() public {
vm.prank(attacker);
profileNFT.mintProfile("Attacker", 25, "img");
vm.prank(victim);
profileNFT.mintProfile("Victim", 26, "img");
// Attacker likes victim
vm.prank(attacker);
likeRegistry.likeUser{value: 1 ether}(victim);
// Victim reciprocates (unaware of attacker's intent)
vm.prank(victim);
likeRegistry.likeUser{value: 1 ether}(attacker);
// MultiSig deployed with attacker + victim as co-owners
// Victim tries to withdraw but attacker never approves
// Funds are permanently locked -- no timeout or recovery
}

Recommended Mitigation

+ // Option 1: Add timeout-based recovery to MultiSig
+ uint256 public constant TIMEOUT = 30 days;
+ uint256 public deployedAt;
+
+ function emergencyWithdraw() external onlyOwners {
+ require(block.timestamp > deployedAt + TIMEOUT, "Timeout not reached");
+ // Allow either owner to withdraw their proportional share after timeout
+ }
+ // Option 2: Add match confirmation step in LikeRegistry
+ mapping(address => mapping(address => bool)) public pendingMatches;
+
+ function confirmMatch(address partner) external {
+ require(pendingMatches[msg.sender][partner], "No pending match");
+ // Only deploy MultiSig after both parties explicitly confirm
+ }
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 3 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!