57735 sc insight whitelist bypass in static mint pricing trusting signed params whitelisted instead of on chain iswhitelisted leads to underpricing and access control violation
Submitted on Oct 28th 2025 at 14:58:46 UTC by @ehappyer for Audit Comp | Belong
The static mint path selects the discounted price by reading params.whitelisted from an off-chain signed message, rather than enforcing the on-chain whitelist mapping. This allows the minting flow to accept the lower whitelisted_mint_price when the signature says the user is whitelisted, even if they are not present in self.nft_node.whitelisted. As a result, non-whitelisted users can pay the discounted price or gain whitelist-only access when the off-chain signer or message construction is permissive.
Vulnerability Details
The _mint_static_price_batch function in the nft.cairo uses an off-chain boolean params.whitelisted to select price, without enforcing the on-chain whitelist mapping. At mint, price is computed as:
let mint_price = if params.whitelisted { self.nft_parameters.whitelisted_mint_price.read()} else { self.nft_parameters.mint_price.read()};
This means the discounted whitelisted_mint_price is granted whenever the signed payload sets params.whitelisted = true, regardless of on-chain state. The isWhitelisted function is not used in the static mint pricing branch; the price decision does not call isWhitelisted(msg.sender) nor cross-check it.
Non-whitelisted users can mint at the discounted price or access whitelist-only phases if they obtain a permissive signed message with whitelisted = true (e.g., signer misconfiguration, lax off-chain filters, compromised signer).
The on-chain whitelist mapping becomes ineffective for pricing and access control in this path; business rules intended to be enforced on-chain are bypassed by off-chain assertions.
Recommended Fix
Gate both price and access using on-chain whitelist membership (e.g., isWhitelisted(msg.sender)) rather than relying solely on off-chain params.whitelisted. If off-chain signatures are used, they should authenticate the caller and immutable parameters, but must not override on-chain membership state. Concretely:
When deciding price: let mint_price = if isWhitelisted(msg.sender) { whitelisted_mint_price } else { mint_price };
When enforcing whitelist-only phases: require isWhitelisted(msg.sender) in the restricted branch.
If keeping signed params, ensure they cannot be used to arbitrarily assert whitelist status; use signatures only to prove integrity of mutable parameters that do not affect access control.
Proof of Concept
Verify whether, in the static-price minting path, it’s possible to exploit the whitelisted = true flag in the signed parameters to receive the whitelist price without actually being whitelisted on-chain, resulting in whitelist bypass and underpricing. Demonstrate that the contract currently determines the price based on the off-chain signed parameters, rather than verifying it via the on-chain isWhitelisted(account) check.
1
Test Code
Add the following test case to src/tests/test_nft.cairo.
If you want, I can produce a minimal patch suggestion (Cairo diff) showing where to add an on-chain isWhitelisted(msg.sender) check in the static-price branch.