Skip to main content
Sigvex

Token-2022 Extension Interaction Hazards Remediation

How to reject or explicitly handle Token-2022 extension combinations whose interaction is undefined, economically exploitable, or privacy-breaking.

Token-2022 Extension Interaction Hazards Remediation

Overview

Related Detector: Token-2022 Extension Interaction Hazards

Token-2022 lets a single mint combine multiple extensions. Each is well-defined alone, but some pairs interact in ways no specification pins down: a transfer hook alongside confidential transfers exposes plaintext amounts the proof was meant to hide, transfer fees alongside interest accrual leave fee math ambiguous, and a permanent delegate alongside a freeze authority creates privilege-escalation surprises. The fix is to decide which combinations your program supports and reject the hazardous ones at the point a mint is registered, rather than discovering the interaction in production.

Before (Vulnerable)

// A mint is accepted with whatever extensions it carries, including
// combinations whose interaction is undefined.
fn register_mint(ctx: Context<RegisterMint>) -> Result<()> {
    store_mint(&ctx)?;
    Ok(())
}

After (Fixed)

use spl_token_2022::extension::{
    BaseStateWithExtensions, ExtensionType, StateWithExtensions,
};
use spl_token_2022::state::Mint;

fn register_mint(ctx: Context<RegisterMint>) -> Result<()> {
    let data = ctx.accounts.mint.try_borrow_data()?;
    let state = StateWithExtensions::<Mint>::unpack(&data)?;
    let extensions = state.get_extension_types()?;

    let has = |e: ExtensionType| extensions.contains(&e);

    require!(
        !(has(ExtensionType::TransferHook) && has(ExtensionType::ConfidentialTransferMint)),
        RegistryError::HookBreaksConfidentiality
    );
    require!(
        !(has(ExtensionType::TransferFeeConfig) && has(ExtensionType::InterestBearingConfig)),
        RegistryError::UndefinedFeeInterestInteraction
    );
    drop(data);

    store_mint(&ctx)?;
    Ok(())
}

This works because the program enumerates the mint’s actual extension set and refuses the pairs whose semantics are unresolved. The check runs at registration, so an unsupported mint never enters the system, and the rejection names the specific hazard so operators understand why the mint was declined.

Alternative Mitigations

  • Allow-list known-good mints. Where the supported asset set is small and curated, validate each mint’s full extension layout against an explicit allow-list instead of denying specific pairs.
  • Document and test a supported combination. If a hazardous pair must be supported, pin down exactly how the interaction is resolved, add integration tests that exercise both extensions together, and warn users when a privacy or fee guarantee no longer holds.

Common Mistakes

  • Checking one extension at a time. Validating that each extension is individually acceptable misses the hazard, which lives in the combination, not the parts.
  • Assuming defaults are safe. A mint created elsewhere can carry any extension set; never assume a registered mint lacks an extension you did not add.
  • Treating the transfer-hook + confidential pair as low risk. That pairing leaks the plaintext the confidential transfer was designed to hide and should be rejected outright unless privacy is explicitly not required.

References