Metadata Authority Transfer
Detects unsafe metadata authority transfers without proper verification of the new authority.
Metadata Authority Transfer
Overview
Remediation Guide: How to Fix Metadata Authority Transfer
The metadata authority transfer detector identifies Solana programs that modify the update_authority field of token metadata accounts without properly validating the current authority or the new authority address. In Metaplex Token Metadata, the update_authority controls who can modify metadata properties such as name, symbol, URI, creators, and royalties. Transferring this authority to an unverified address can result in complete loss of control over the token’s identity.
Why This Is an Issue
The update_authority field in Metaplex metadata is the single gatekeeper for all metadata modifications. If a program transfers this authority without proper checks, an attacker can:
- Steal NFT identity by changing name, image, and attributes to impersonate high-value collections
- Bypass collection verification by altering collection membership fields
- Manipulate royalties by changing creator arrays and fee percentages
- Cause permanent loss of control if the authority is set to a dead or attacker-controlled address
Multiple incidents in 2021-2022 involved unauthorized metadata updates on Solana NFTs where missing authority checks allowed attackers to modify token properties.
CWE mapping: CWE-863 (Incorrect Authorization), CWE-284 (Improper Access Control).
How to Resolve
Native Rust
use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError};
pub fn transfer_metadata_authority(accounts: &[AccountInfo]) -> ProgramResult {
let current_authority = &accounts[0];
let metadata_account = &accounts[1];
let new_authority = &accounts[2];
// FIXED: verify current authority signed the transaction
if !current_authority.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
// FIXED: verify the current authority matches the metadata's update_authority
let metadata = deserialize_metadata(&metadata_account.data.borrow())?;
if metadata.update_authority != *current_authority.key {
return Err(ProgramError::InvalidAccountData);
}
// FIXED: validate new authority is a known, expected address
if !is_approved_authority(new_authority.key) {
return Err(ProgramError::InvalidArgument);
}
// Proceed with authority transfer via Metaplex CPI
Ok(())
}
Anchor
use anchor_lang::prelude::*;
#[derive(Accounts)]
pub struct TransferAuthority<'info> {
#[account(
constraint = metadata.update_authority == current_authority.key()
@ ErrorCode::InvalidUpdateAuthority
)]
pub metadata: Account<'info, MetadataAccount>,
pub current_authority: Signer<'info>,
/// CHECK: validated via constraint or allowlist
pub new_authority: AccountInfo<'info>,
}
Examples
Vulnerable Code
pub fn set_new_authority(accounts: &[AccountInfo], new_auth: Pubkey) -> ProgramResult {
let metadata_account = &accounts[0];
// VULNERABLE: no signer check, no authority match, no new authority validation
let mut metadata = deserialize_metadata(&metadata_account.data.borrow())?;
metadata.update_authority = new_auth;
serialize_metadata(&metadata, &mut metadata_account.data.borrow_mut())?;
Ok(())
}
Fixed Code
pub fn set_new_authority(accounts: &[AccountInfo], new_auth: Pubkey) -> ProgramResult {
let current_auth = &accounts[0];
let metadata_account = &accounts[1];
if !current_auth.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
let mut metadata = deserialize_metadata(&metadata_account.data.borrow())?;
if metadata.update_authority != *current_auth.key {
return Err(ProgramError::InvalidAccountData);
}
metadata.update_authority = new_auth;
serialize_metadata(&metadata, &mut metadata_account.data.borrow_mut())?;
Ok(())
}
Sample Sigvex Output
{
"detector_id": "metadata-authority-transfer",
"severity": "high",
"confidence": 0.80,
"description": "Metadata authority being transferred without proper validation. Current authority not verified as signer; new authority address not validated.",
"location": { "function": "set_new_authority", "offset": 3 }
}
Detection Methodology
The detector analyzes program functions in two passes:
- Validation tracking: Identifies signer checks, key comparisons, and owner validations across all basic blocks.
- Authority write detection: Locates writes to metadata account data at offsets corresponding to the
update_authorityfield (offsets 1-32 in the Metaplex metadata layout). - CPI analysis: Identifies cross-program invocations to the Metaplex Token Metadata program that perform authority updates.
- Gap analysis: Reports findings when authority writes or CPIs occur without corresponding validation of both the current and new authority.
Context modifiers reduce confidence for admin/initialization functions and Anchor programs where higher-level constraints may provide protection.
Limitations
- The detector uses offset heuristics to identify authority field writes, which may produce false positives for non-authority data at similar offsets.
- CPI-based authority transfers through the Metaplex program are conservatively identified; some safe patterns may be flagged.
- Authority validation via called helper functions (not inlined) may not be tracked without inter-procedural analysis.
Related Detectors
- Metaplex Authority Bypass — detects missing update authority verification in metadata operations
- Missing Signer Check — detects missing signer verification on privileged operations
- SPL Token Metadata Validation — detects metadata PDA and URI validation issues