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.
Recommended Fix
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 Metaplex CPI instead of direct writes
Delegate authority changes to the Metaplex Token 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);