Skip to main content
Sigvex

Metadata Authority Transfer Remediation

How to fix unsafe metadata authority transfers in Solana programs.

Metadata Authority Transfer Remediation

Overview

Related Detector: Metadata Authority Transfer

An unsafe metadata authority transfer allows any caller to change the update_authority on a token metadata account, granting full control over the token’s name, image, creators, and royalties. The fix requires verifying that the current authority signed the transaction and that the new authority is a validated address.

Before (Vulnerable)

pub fn transfer_authority(accounts: &[AccountInfo], new_auth: Pubkey) -> ProgramResult {
    let metadata_account = &accounts[0];

    // VULNERABLE: no signer check, no authority match
    let mut metadata = deserialize_metadata(&metadata_account.data.borrow())?;
    metadata.update_authority = new_auth;
    serialize_metadata(&metadata, &mut metadata_account.data.borrow_mut())?;
    Ok(())
}

After (Fixed)

pub fn transfer_authority(accounts: &[AccountInfo], new_auth: Pubkey) -> ProgramResult {
    let current_auth = &accounts[0];
    let metadata_account = &accounts[1];

    // Verify current authority signed the transaction
    if !current_auth.is_signer {
        return Err(ProgramError::MissingRequiredSignature);
    }

    // Verify the signer is the actual update authority
    let mut metadata = deserialize_metadata(&metadata_account.data.borrow())?;
    if metadata.update_authority != *current_auth.key {
        return Err(ProgramError::InvalidAccountData);
    }

    // Validate new authority (e.g., against an allowlist or governance address)
    if !is_approved_authority(&new_auth) {
        return Err(ProgramError::InvalidArgument);
    }

    metadata.update_authority = new_auth;
    serialize_metadata(&metadata, &mut metadata_account.data.borrow_mut())?;
    Ok(())
}

The fix adds three checks: signer verification, authority matching, and new authority validation.

Alternative Mitigations

1. Use the NFT metadata program CPI instead of direct writes

Delegate authority changes to the standard NFT metadata program, which enforces its own authority checks:

// Use UpdateMetadataAccountV2 instruction which enforces update_authority check
let ix = mpl_token_metadata::instruction::update_metadata_accounts_v2(
    mpl_token_metadata::id(),
    metadata_key,
    current_authority.key(),
    Some(new_authority),
    None, // data
    None, // primary_sale_happened
);
invoke(&ix, &[metadata_account.clone(), current_authority.clone()])?;

2. Make authority immutable

For tokens that should never change authority, set is_mutable to false after initial configuration.

3. Multi-signature authority transfer

Require multiple signatures for authority changes using a governance program or multisig.

Common Mistakes

Mistake 1: Checking signer but not matching against metadata authority

// INCOMPLETE: any signer can transfer authority
if !accounts[0].is_signer {
    return Err(ProgramError::MissingRequiredSignature);
}
// Missing: check that accounts[0].key == metadata.update_authority

Mistake 2: Accepting arbitrary new authority from instruction data

// RISKY: new authority comes directly from untrusted instruction data
let new_auth = Pubkey::new_from_array(instruction_data[0..32].try_into()?);
metadata.update_authority = new_auth; // No validation of new_auth

Mistake 3: Not checking is_mutable before authority transfer

// WRONG: attempting to change authority on immutable metadata
// Should check: require!(metadata.is_mutable, ErrorCode::MetadataImmutable);

References