Skip to main content
Sigvex

SPL Token Authority Confusion Remediation

How to fix authority confusion between owner, delegate, close, mint, and freeze authorities.

SPL Token Authority Confusion Remediation

Overview

Related Detector: SPL Token Authority Confusion

Authority confusion occurs when a program checks the wrong authority for a token operation – for example, validating owner but performing a close that requires close_authority, or using Transfer instead of TransferChecked. The fix requires matching each operation to its correct authority and using checked instruction variants.

Before (Vulnerable)

// Uses Transfer (no decimal check) and doesn't verify authority
let ix = spl_token::instruction::transfer(
    &spl_token::id(), source.key, dest.key, authority.key, &[], amount,
)?;
invoke(&ix, accounts)?;

After (Fixed)

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

// Use TransferChecked for decimal validation
let ix = spl_token::instruction::transfer_checked(
    &spl_token::id(), source.key, mint.key, dest.key,
    authority.key, &[], amount, expected_decimals,
)?;
invoke(&ix, &[source.clone(), mint.clone(), dest.clone(), authority.clone()])?;

Alternative Mitigations

1. Anchor typed accounts

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

the account-validation framework’s token::authority constraint validates the correct authority at deserialization.

2. Explicit authority type matching

For operations that can use either owner or delegate, explicitly check which authority type is provided:

let token_data = Account::unpack(&token_account.data.borrow())?;
if *authority.key == token_data.owner {
    // Owner transfer -- full balance available
} else if token_data.delegate == COption::Some(*authority.key) {
    // Delegated transfer -- limited to delegated_amount
    require!(amount <= token_data.delegated_amount, InsufficientDelegation);
} else {
    return Err(ProgramError::InvalidAccountData);
}

Common Mistakes

Mistake 1: Using Transfer instead of TransferChecked

// WRONG: Transfer does not validate decimals
spl_token::instruction::transfer(...)?;
// CORRECT: TransferChecked validates decimal precision
spl_token::instruction::transfer_checked(..., amount, decimals)?;

Mistake 2: Confusing mint_authority with freeze_authority

// WRONG: using freeze_authority to mint tokens
// mint_authority and freeze_authority are different fields on the Mint account

Mistake 3: Using owner for close when close_authority is set

// WRONG: if close_authority is set, owner cannot close
// Must check: if token_account.close_authority.is_some(), use that authority

References