Skip to main content
Sigvex

the Metaplex Token Metadata program Authority Bypass

Detects missing update authority verification in canonical NFT metadata operations.

Metaplex Authority Bypass

Overview

Remediation Guide: How to Fix Metaplex Authority Bypass

The Metaplex Token Metadata program authority bypass detector identifies Solana programs that invoke the canonical NFT metadata program instructions or modify metadata account data without verifying that the transaction signer is the designated update_authority. This allows unauthorized modification of NFT metadata including name, symbol, URI, creator arrays, and royalty settings.

Why This Is an Issue

canonical NFT metadata operations require the update_authority to authorize changes. When a program makes CPI calls to the metadata program without first verifying the caller holds the correct authority, attackers can:

  • Modify NFT metadata (name, image, attributes) to impersonate valuable collections
  • Bypass collection verification, adding unauthorized NFTs to verified collections
  • Alter creator arrays and royalty percentages to redirect fees
  • Change supposedly immutable metadata by skipping is_mutable flag checks

Multiple Solana NFT marketplace incidents in 2021-2022 exploited missing authority verification to perform unauthorized metadata updates and collection verification bypasses.

CWE mapping: CWE-862 (Missing Authorization), CWE-285 (Improper Authorization).

How to Resolve

Native Rust

pub fn update_nft_metadata(accounts: &[AccountInfo]) -> ProgramResult {
    let authority = &accounts[0];
    let metadata_account = &accounts[1];

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

    // Verify the 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 mutable before allowing changes
    if !metadata.is_mutable {
        return Err(MetadataError::DataIsImmutable.into());
    }

    // Safe to proceed with the Metaplex Token Metadata program CPI
    Ok(())
}

Anchor

#[derive(Accounts)]
pub struct UpdateMetadata<'info> {
    pub authority: Signer<'info>,
    #[account(
        constraint = metadata.update_authority == authority.key()
            @ ErrorCode::InvalidUpdateAuthority,
        constraint = metadata.is_mutable @ ErrorCode::MetadataImmutable
    )]
    pub metadata: Account<'info, MetadataAccount>,
    pub metadata_program: Program<'info, MplTokenMetadata>,
}

Examples

Vulnerable Code

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

    // VULNERABLE: no authority check before CPI to metadata program
    let ix = update_metadata_accounts_v2(
        *metadata_program.key,
        *metadata_account.key,
        *accounts[2].key, // authority -- not validated!
        None,
        Some(DataV2 { uri: new_uri, ..Default::default() }),
        None,
    );
    invoke(&ix, accounts)?;
    Ok(())
}

Fixed Code

pub fn update_metadata_uri(accounts: &[AccountInfo], new_uri: String) -> ProgramResult {
    let authority = &accounts[2];

    // FIXED: verify authority signed and matches metadata
    if !authority.is_signer {
        return Err(ProgramError::MissingRequiredSignature);
    }
    let metadata = Metadata::from_account_info(&accounts[1])?;
    if metadata.update_authority != *authority.key {
        return Err(MetadataError::InvalidUpdateAuthority.into());
    }
    if !metadata.is_mutable {
        return Err(MetadataError::DataIsImmutable.into());
    }

    // Now safe to CPI
    let ix = update_metadata_accounts_v2(/* ... */);
    invoke(&ix, accounts)?;
    Ok(())
}

Sample Sigvex Output

{
  "detector_id": "metaplex-authority-bypass",
  "severity": "high",
  "confidence": 0.80,
  "description": "Cross-program invocation to the canonical NFT metadata program occurs without verifying that the transaction signer is the designated update authority.",
  "location": { "function": "update_metadata_uri", "offset": 1 }
}

Detection Methodology

The detector performs multi-pass analysis:

  1. Function name filtering: Skips functions whose names do not suggest metadata operations (e.g., update_metadata, verify_collection, set_authority).
  2. Authority check tracking: Identifies CheckSigner and CheckOwner statements as evidence of authority validation.
  3. CPI detection: Locates cross-program invocations that may target the canonical NFT metadata program and flags those lacking prior authority checks.
  4. Direct write detection: Identifies direct writes to metadata account data without authority verification.
  5. Heuristic analysis: Functions named for metadata updates that contain no security checks are flagged separately.

Limitations

  • Function name heuristics may miss metadata operations with non-standard names.
  • The detector cannot verify that a CPI target is specifically the canonical NFT program without program ID constants in the bytecode.
  • Anchor programs using has_one = update_authority constraints may be flagged at reduced confidence if the constraint is not visible in decompiled output.

References