Root + Impact
Description
function mintProfile(
string memory name,
uint8 age,
string memory profileImage
) external {
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);
}
Risk
Likelihood:
Any user can mint a profile with age 0 or 17 — this requires no exploit, just calling the function with a low value.
Automated bot registrations or tests commonly use age 0 or 1 as default values.
Impact:
Profiles with ages below 18 are permanently stored on-chain and encoded in immutable token URIs.
Protocol faces legal liability in jurisdictions requiring age verification for dating platforms (COPPA, GDPR, EU DSA).
Even if the frontend enforces age limits, the smart contract is the source of truth — direct contract interactions bypass any UI-layer checks.
Proof of Concept
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "../src/SoulboundProfileNFT.sol";
contract NoAgeValidationTest is Test {
SoulboundProfileNFT nft;
address alice = makeAddr("alice");
function setUp() public {
nft = new SoulboundProfileNFT();
}
function test_underage_profile_accepted() public {
vm.prank(alice);
nft.mintProfile("Alice", 0, "ipfs://alice");
uint256 tokenId = nft.profileToToken(alice);
assertTrue(tokenId != 0, "Profile minted with age 0");
}
function test_age_17_accepted() public {
vm.prank(alice);
nft.mintProfile("Alice", 17, "ipfs://alice");
assertTrue(nft.profileToToken(alice) != 0);
}
function test_impossible_age_accepted() public {
vm.prank(alice);
nft.mintProfile("Alice", 255, "ipfs://alice");
assertTrue(nft.profileToToken(alice) != 0);
}
}
Recommended Mitigation
Add minimum and maximum age bounds to mintProfile():
uint8 public constant MIN_AGE = 18;
uint8 public constant MAX_AGE = 120;
function mintProfile(
string memory name,
uint8 age,
string memory profileImage
) external {
require(profileToToken[msg.sender] == 0, "Profile already exists");
require(age >= MIN_AGE, "Must be at least 18 years old");
require(age <= MAX_AGE, "Invalid age");
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);
}