Skip to main content
Sigvex

SPL Token Authority Confusion

Detects confusion between token account authorities (owner, delegate, close authority, mint authority, freeze authority).

SPL Token Authority Confusion

Overview

Remediation Guide: How to Fix SPL Token Authority Confusion

The SPL Token authority confusion detector identifies programs that misuse token account authorities – confusing owner with delegate, close authority with owner, or mint authority with freeze authority. SPL Token accounts have multiple distinct authorities, and using the wrong one for a given operation can lead to unauthorized transfers, account closures, or token minting.

Why This Is an Issue

SPL Token accounts maintain several authority fields: owner (controls transfers), delegate (can transfer up to delegated_amount), close_authority (can close the account), and at the mint level, mint_authority and freeze_authority. Confusing these leads to:

  • Unauthorized transfers: Checking owner but using delegate’s allowance, or vice versa
  • Unauthorized account closure: Using the wrong authority for CloseAccount operations
  • Unauthorized minting: Confusing mint_authority with freeze_authority, allowing the freeze authority holder to mint tokens
  • Unchecked variants: Using Transfer instead of TransferChecked skips decimal validation

CWE mapping: CWE-863 (Incorrect Authorization), CWE-682 (Incorrect Calculation).

How to Resolve

Native Rust

pub fn transfer_tokens(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
    let source = &accounts[0];
    let destination = &accounts[1];
    let authority = &accounts[2];
    let mint = &accounts[3];

    // Verify authority is a signer
    if !authority.is_signer {
        return Err(ProgramError::MissingRequiredSignature);
    }

    // Use TransferChecked to validate decimals
    let ix = spl_token::instruction::transfer_checked(
        &spl_token::id(), source.key, mint.key, destination.key,
        authority.key, &[], amount, 6, // explicit decimals
    )?;
    invoke(&ix, &[source.clone(), mint.clone(), destination.clone(), authority.clone()])?;
    Ok(())
}

Anchor

#[derive(Accounts)]
pub struct TransferTokens<'info> {
    #[account(mut, token::authority = authority)]
    pub source: Account<'info, TokenAccount>,
    #[account(mut)]
    pub destination: Account<'info, TokenAccount>,
    pub authority: Signer<'info>,
}

Examples

Vulnerable Code

pub fn unsafe_transfer(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
    let source = &accounts[0];
    let dest = &accounts[1];
    let authority = &accounts[2]; // Not validated as signer!

    // VULNERABLE: Using Transfer instead of TransferChecked -- no decimal validation
    // VULNERABLE: authority not verified as signer
    let ix = spl_token::instruction::transfer(
        &spl_token::id(), source.key, dest.key, authority.key, &[], amount,
    )?;
    invoke(&ix, &[source.clone(), dest.clone(), authority.clone()])?;
    Ok(())
}

Fixed Code

pub fn safe_transfer(accounts: &[AccountInfo], amount: u64, decimals: u8) -> ProgramResult {
    let authority = &accounts[2];
    if !authority.is_signer {
        return Err(ProgramError::MissingRequiredSignature);
    }

    // Use TransferChecked for decimal validation
    let ix = spl_token::instruction::transfer_checked(
        &spl_token::id(), accounts[0].key, accounts[3].key,
        accounts[1].key, authority.key, &[], amount, decimals,
    )?;
    invoke(&ix, accounts)?;
    Ok(())
}

Sample Sigvex Output

{
  "detector_id": "spl-token-authority-confusion",
  "severity": "high",
  "confidence": 0.78,
  "description": "Token transfer uses authority without validating it as a signer. The authority could be the token owner or a delegate -- ensure the correct one is checked.",
  "location": { "function": "unsafe_transfer", "offset": 1 }
}

Detection Methodology

  1. Validation tracking: Collects signer and owner checks across all basic blocks.
  2. Operation classification: Identifies Transfer, TransferChecked, CloseAccount, Approve, MintTo, FreezeAccount, and ThawAccount operations from CPI instruction discriminators.
  3. Authority analysis: For each operation, extracts the authority account and verifies it has been validated appropriately for that specific operation type.
  4. Checked variant detection: Flags use of Transfer instead of TransferChecked, which skips decimal validation.

Limitations

  • Simplified account index extraction treats the entire accounts expression as a single variable; multi-account arrays require richer HIR support.
  • Authority validation via helper functions called before the CPI may not be tracked.
  • Anchor programs with token::authority constraints provide implicit validation that may not be detected.

References