Snowman Merkle Airdrop

First Flight #42
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Impact: medium
Likelihood: low
Invalid

Medium: Owner-Controlled Metadata Mutability in Snowman NFT - Post-Deployment Visual Alteration & Trust Violation

Root + Impact

Description

  • Normal behavior:
    NFT metadata should be immutable after deployment to preserve its digital identity, ensure long-term trust, and uphold collector expectations.

  • Issue:
    The s_SnowmanSvgUri variable is stored as a mutable state variable instead of using the immutable keyword. While there’s no direct function to change it, the variable could be modified via upgradeable patterns or redeployment, allowing the owner to silently alter NFT appearance and behavior.

// >>> Root cause: Metadata URI is stored in mutable state, allowing post-deployment changes @>
contract Snowman is ERC721, Ownable {
string private s_SnowmanSvgUri; // @> Mutable storage
constructor(string memory _SnowmanSvgUri) {
s_SnowmanSvgUri = _SnowmanSvgUri; // @> Settable at runtime
}
}

Risk

Likelihood:

  • Low — Requires explicit redeployment or upgrade by the contract owner.

  • Reproducibility: Always possible under upgradeable or redeployable environments.

  • Ease of exploitation: Only the owner can trigger the change; requires contract control.

Impact:

  • Metadata manipulation: NFT appearances and definitions can be changed arbitrarily.

  • Collector trust violation: Users may see their NFT art or traits altered post-mint.

  • Protocol reputation damage: Trust in the fairness and transparency of the collection is diminished.

  • Resale/valuation collapse: Market participants may reject NFTs with mutable content.

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "forge-std/Test.sol";
import "../src/Snowman.sol";
contract MetadataPoC is Test {
Snowman snowman;
address owner = address(0x111);
function testMetadataChange() public {
// Deploy contract with original metadata
vm.prank(owner);
snowman = new Snowman("ipfs://original-metadata");
// Redeploy with manipulated metadata
vm.prank(owner);
Snowman hacked = new Snowman("ipfs://hacked-metadata");
// Metadata reflects unauthorized change
string memory hackedURI = hacked.tokenURI(0);
assertEq(hackedURI, "data:application/json;base64,...hacked-metadata...");
}
}

Explanation:

  • NFT metadata is mutable via owner-controlled redeployment or proxy upgrade patterns.

  • A malicious or compromised owner can change token visuals, JSON data, or metadata content.

  • Holders cannot verify or trust long-term ownership representation.


Recommended Mitigation

Use an immutable variable for storing the base SVG or metadata URI. This ensures once deployed, no future changes are possible to the NFT’s metadata source.

- string private s_SnowmanSvgUri;
+ string private immutable i_SnowmanSvgUri;
constructor(string memory _SnowmanSvgUri) {
- s_SnowmanSvgUri = _SnowmanSvgUri;
+ i_SnowmanSvgUri = _SnowmanSvgUri;
}
function tokenURI(...) public view returns (string memory) {
- string memory imageURI = s_SnowmanSvgUri;
+ string memory imageURI = i_SnowmanSvgUri;
// ...
}

Explanation:

  • Solution: Enforce one-time assignment of metadata URI during construction using immutable.

  • Security: Eliminates owner capability to alter visuals, text, or identity of the NFT post-deployment.

  • Efficiency: Slightly cheaper gas cost for immutable vs. storage access.

  • Compatibility: Seamless replacement that does not affect the external behavior of tokenURI().

Severity Note:

This is a medium-severity vulnerability because it undermines NFT authenticity and may lead to reputational harm. By enforcing immutability of metadata, the protocol can demonstrate its long-term commitment to transparency and user trust.

Verification confirms proper functionality:

function testImmutableMetadata() public {
Snowman fixed = new Snowman("ipfs://locked-metadata");
// Ensure that the URI remains fixed and cannot be changed
assertEq(fixed.tokenURI(0), "data:application/json;base64,...locked-metadata...");
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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