Skip to main content
Sigvex

the NFT metadata program Authority Bypass Remediation

How to fix missing update authority verification in standard NFT metadata operations.

the NFT metadata program Authority Bypass Remediation

Overview

Related Detector: the NFT metadata program Authority Bypass

A NFT metadata authority bypass allows unauthorized callers to modify NFT metadata – name, symbol, URI, creators, royalties – by invoking the standard NFT metadata program instructions without the update_authority signing the transaction. The fix requires verifying the signer matches the metadata’s update_authority and checking the is_mutable flag before any modification.

Before (Vulnerable)

pub fn update_nft_uri(accounts: &[AccountInfo], new_uri: String) -> ProgramResult {
    let metadata_program = &accounts[0];
    let metadata = &accounts[1];
    let authority = &accounts[2];

    // VULNERABLE: no check that authority == metadata.update_authority
    // VULNERABLE: no is_mutable check
    let ix = update_metadata_accounts_v2(
        *metadata_program.key, *metadata.key, *authority.key,
        None, Some(DataV2 { uri: new_uri, ..Default::default() }), None,
    );
    invoke(&ix, accounts)?;
    Ok(())
}

After (Fixed)

pub fn update_nft_uri(accounts: &[AccountInfo], new_uri: String) -> ProgramResult {
    let metadata_program = &accounts[0];
    let metadata_account = &accounts[1];
    let authority = &accounts[2];

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

    // Verify signer matches the metadata's update_authority
    let metadata = Metadata::from_account_info(metadata_account)?;
    if metadata.update_authority != *authority.key {
        return Err(MetadataError::InvalidUpdateAuthority.into());
    }

    // Verify metadata is still mutable
    if !metadata.is_mutable {
        return Err(MetadataError::DataIsImmutable.into());
    }

    let ix = update_metadata_accounts_v2(
        *metadata_program.key, *metadata_account.key, *authority.key,
        None, Some(DataV2 { uri: new_uri, ..Default::default() }), None,
    );
    invoke(&ix, accounts)?;
    Ok(())
}

Alternative Mitigations

1. Anchor has_one constraint

#[derive(Accounts)]
pub struct UpdateMetadata<'info> {
    pub authority: Signer<'info>,
    #[account(has_one = update_authority @ ErrorCode::Unauthorized)]
    pub metadata: Account<'info, MetadataAccount>,
}

2. Collection authority delegation

For collection-level operations, use the NFT metadata program’s ApproveCollectionAuthority to delegate specific capabilities instead of sharing the update authority.

3. Make metadata immutable after setup

Call UpdateMetadataAccountV2 with is_mutable: false once metadata is finalized to prevent future modifications entirely.

Common Mistakes

Mistake 1: Checking signer but not matching against update_authority

// INCOMPLETE: any signer can modify metadata
if !authority.is_signer { return Err(...); }
// Missing: authority.key == metadata.update_authority

Mistake 2: Skipping is_mutable check

// RISKY: allows modifying metadata marked as immutable
// The NFT metadata program itself checks is_mutable, but your program
// should validate first to provide clear error messages

Mistake 3: Using UncheckedAccount for authority in Anchor

// WRONG: bypasses the account-validation framework's automatic signer validation
pub authority: UncheckedAccount<'info>, // Should be Signer<'info>

References