Title: blockProfile centralization allows owner to lock user funds permanently
Severity: Medium
Impact: Owner can block any user, preventing them from matching and locking their ETH.
Likelihood: Medium — requires malicious or compromised owner, but no safeguards exist.
Reference Files: src/SoulboundProfileNFT.sol:57-66, src/LikeRegistry.sol:34-36
The contract owner can call blockProfile on any user, burning their profile NFT. Without a profile, the user cannot call likeUser (which requires profileToToken[msg.sender] != 0), preventing them from matching and recovering their ETH. The vulnerable code:
After blocking, the user loses all platform access. Any ETH they spent on likes or received from matches is trapped — they cannot initiate or complete matches without a profile.
Impact: Medium. A blocked user who has paid ETH for likes can never match and recover their funds. Their ETH sits in the LikeRegistry contract with no withdrawal mechanism.
Likelihood: Medium. Requires the owner to act maliciously or be compromised. No timelock, multisig, or governance protects against unilateral blocking.
A user who spent 5 ETH liking multiple people is blocked by the owner. They lose their 5 ETH permanently with no recourse and cannot complete any pending matches.
Owner blocks Bob, preventing him from completing the mutual match — Alice's 1 ETH is permanently locked.
Add a withdrawal mechanism for users to recover their ETH when blocked, or implement a timelock/governance for blockProfile:
This allows blocked users to recover their contributed ETH, preventing permanent fund lock while maintaining the owner's ability to moderate the platform.
## Description App owner can block users at will, causing users to have their funds locked. ## Vulnerability Details `SoulboundProfileNFT::blockProfile` can block any app's user at will. ```js /// @notice App owner can block users function blockProfile(address blockAddress) external onlyOwner { uint256 tokenId = profileToToken[blockAddress]; require(tokenId != 0, "No profile found"); _burn(tokenId); delete profileToToken[blockAddress]; delete _profiles[tokenId]; emit ProfileBurned(blockAddress, tokenId); } ``` ## Proof of Concept The following code demonstrates the scenario where the app owner blocks `bob` and he is no longer able to call `LikeRegistry::likeUser`. Since the contract gives no posibility of fund withdrawal, `bob`'s funds are now locked. Place `test_blockProfileAbuseCanCauseFundLoss` in `testSoulboundProfileNFT.t.sol`: ```js function test_blockProfileAbuseCanCauseFundLoss() public { vm.deal(bob, 10 ether); vm.deal(alice, 10 ether); // mint a profile NFT for bob vm.prank(bob); soulboundNFT.mintProfile("Bob", 25, "ipfs://profileImage"); // mint a profile NFT for Alice vm.prank(alice); soulboundNFT.mintProfile("Alice", 25, "ipfs://profileImage"); // alice <3 bob vm.prank(alice); likeRegistry.likeUser{value: 1 ether}(bob); vm.startPrank(owner); soulboundNFT.blockProfile(bob); assertEq(soulboundNFT.profileToToken(msg.sender), 0); vm.startPrank(bob); vm.expectRevert("Must have a profile NFT"); // bob is no longer able to like a user, as his profile NFT is deleted // his funds are effectively locked likeRegistry.likeUser{value: 1 ether}(alice); } ``` And run the test: ```bash $ forge test --mt test_blockProfileAbuseCanCauseFundLoss Ran 1 test for test/testSoulboundProfileNFT.t.sol:SoulboundProfileNFTTest [PASS] test_blockProfileAbuseCanCauseFundLoss() (gas: 326392) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.42ms (219.63µs CPU time) Ran 1 test suite in 140.90ms (1.42ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests) ``` ## Impact App users can have their funds locked, as well as miss out on potential dates. ## Recommendations Add a voting mechanism to prevent abuse and/or centralization of the feature.
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.