DatingDapp

AI First Flight #6
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Severity: medium
Valid

blockProfile Does Not Prevent Profile Recreation — Block Bypass Trivial

Missing Persistent Blocklist in blockProfile Allows Blocked Users to Re-mint

Description

  • blockProfile is the owner's moderation tool — it is intended to permanently remove a malicious or abusive user from the platform by burning their NFT.

  • blockProfile calls delete profileToToken[blockAddress], which resets the mapping value to 0. Since mintProfile only gates on require(profileToToken[msg.sender] ==
    0, "Profile already exists"), a blocked address immediately satisfies that check again and can re-mint a fresh profile in the very next transaction, completely
    bypassing the block.

function blockProfile(address blockAddress) external onlyOwner {
uint256 tokenId = profileToToken[blockAddress];
require(tokenId != 0, "No profile found");

  _burn(tokenId);                                       

@> delete profileToToken[blockAddress]; // resets to 0 — no persistent record of block
delete _profiles[tokenId];

  emit ProfileBurned(blockAddress, tokenId);                                                                                                                       

}

function mintProfile(string memory name, uint8 age, string memory profileImage) external {
@> require(profileToToken[msg.sender] == 0, "Profile already exists"); // passes immediately after block
uint256 tokenId = ++_nextTokenId;
_safeMint(msg.sender, tokenId);
_profiles[tokenId] = Profile(name, age, profileImage);
profileToToken[msg.sender] = tokenId;
emit ProfileMinted(msg.sender, tokenId, name, age, profileImage);
}

Risk

Likelihood:

  • Any blocked user re-mints their profile in one transaction immediately after being blocked — the bypass requires zero special knowledge or capital.

  • Admin-blocked users who re-mint retain all their prior likes mappings in LikeRegistry, re-entering the protocol with their full old like history intact.

Impact:

  • The only moderation mechanism in the protocol is rendered completely ineffective — blocked users regain full platform access immediately after removal.

  • A malicious user blocked for abuse, spam, or fraud can continue their behaviour indefinitely with no friction beyond a single gas fee per block.

Proof of Concept

function test_M01_BlockedUserCanRemint() public {
vm.prank(user);
soulboundNFT.mintProfile("Alice", 18, "ipfs://profileImageAlice");

  // Owner blocks the user                                                                                                                                         
  vm.prank(owner);                                                                                                                                                 
  soulboundNFT.blockProfile(user);                                                                                                                                 
  assertEq(soulboundNFT.profileToToken(user), 0);       

  // Blocked user immediately re-mints — no error                                                                                                                  
  vm.prank(user);
  soulboundNFT.mintProfile("Alice", 18, "ipfs://profileImageAlice");                                                                                               
                                                        
  // Block had zero effect                                                                                                                                         
  assertEq(soulboundNFT.profileToToken(user), 2);

}

Recommended Mitigation

  • mapping(address => bool) public isBlocked;

function mintProfile(string memory name, uint8 age, string memory profileImage) external {

  • require(!isBlocked[msg.sender], "Account is permanently blocked");
    require(profileToToken[msg.sender] == 0, "Profile already exists");

    uint256 tokenId = ++_nextTokenId;
    _safeMint(msg.sender, tokenId);
    _profiles[tokenId] = Profile(name, age, profileImage);
    profileToToken[msg.sender] = tokenId;
    emit ProfileMinted(msg.sender, tokenId, name, age, profileImage);
    }

function blockProfile(address blockAddress) external onlyOwner {
uint256 tokenId = profileToToken[blockAddress];
require(tokenId != 0, "No profile found");

  _burn(tokenId);
  delete profileToToken[blockAddress];                                                                                                                             
  delete _profiles[tokenId];                            
  • isBlocked[blockAddress] = true;

    emit ProfileBurned(blockAddress, tokenId);
    }

Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 4 hours ago
Submission Judgement Published
Validated
Assigned finding tags:

[M-01] `SoulboundProfileNFT::blockProfile` make it possible to recreate the profile

## Description The `SoulboundProfileNFT::blockProfile` function uses `delete profileToToken[blockAddress]`, which resets `profileToToken[blockAddress]` to `0`. Since the mintProfile function checks for an existing profile by verifying that `profileToToken[msg.sender] == 0`, a blocked account can be recreated by simply minting a new profile. This behavior bypasses the intended permanent block functionality. ## Vulnerability Details By deleting the mapping entry for a blocked account, the contract inadvertently allows a new mintProfile call to pass the check `require(profileToToken[msg.sender] == 0, "Profile already exists")`. Essentially, once an account is blocked, its associated mapping entry is cleared, so the condition to identify an account with an existing profile is no longer met. This loophole enables a blocked account to recreate its profile, undermining the purpose of blocking. ## Impact A blocked account, which should be permanently barred from engaging with the platform, can circumvent this restriction by re-minting its profile. The integrity of the platform is compromised, as blocked users could regain access and potentially perform further malicious actions. ## POC ```solidity function testRecereationOfBlockedAccount() public { // Alice mints a profile successfully vm.prank(user); soulboundNFT.mintProfile("Alice", 18, "ipfs://profileImageAlice"); // Owner blocks Alice's account, which deletes Alice profile mapping vm.prank(owner); soulboundNFT.blockProfile(user); // The blocked user (Alice) attempts to mint a new profile. // Due to the reset mapping value (0), the require check is bypassed. vm.prank(user); soulboundNFT.mintProfile("Alice", 18, "ipfs://profileImageAlice"); } ``` ## Recommendations - When blocking an account, implement a mechanism to permanently mark that address as blocked rather than simply deleting an entry. For example, maintain a separate mapping (e.g., isBlocked) to record blocked accounts, and update mintProfile to check if an account is permanently barred from minting: Example modification: ```diff + mapping(address => bool) public isBlocked; ... function mintProfile(string memory name, uint8 age, string memory profileImage) external { + require(!isBlocked[msg.sender], "Account is permanently blocked"); require(profileToToken[msg.sender] == 0, "Profile already exists"); uint256 tokenId = ++_nextTokenId; _safeMint(msg.sender, tokenId); // Store metadata on-chain _profiles[tokenId] = Profile(name, age, profileImage); profileToToken[msg.sender] = tokenId; emit ProfileMinted(msg.sender, tokenId, name, age, profileImage); } ... function blockProfile(address blockAddress) external onlyOwner { uint256 tokenId = profileToToken[blockAddress]; require(tokenId != 0, "No profile found"); _burn(tokenId); delete profileToToken[blockAddress]; delete _profiles[tokenId]; + isBlocked[blockAddress] = true; emit ProfileBurned(blockAddress, tokenId); } ```

Support

FAQs

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

Give us feedback!